From 13d05bc8458758ee39cb829098241e89616717ee Mon Sep 17 00:00:00 2001 From: Ashlee Young Date: Wed, 9 Sep 2015 22:15:21 -0700 Subject: ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60 Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd --- framework/src/onos/web/api/pom.xml | 114 + .../rest/exceptions/AbstractMapper.java | 77 + .../rest/exceptions/BadRequestMapper.java | 32 + .../rest/exceptions/EntityNotFoundMapper.java | 32 + .../exceptions/IllegalArgumentExceptionMapper.java | 31 + .../exceptions/IllegalStateExceptionMapper.java | 31 + .../rest/exceptions/NotFoundMapper.java | 34 + .../rest/exceptions/ServerErrorMapper.java | 30 + .../rest/exceptions/ServiceNotFoundMapper.java | 32 + .../exceptions/WebApplicationExceptionMapper.java | 45 + .../onosproject/rest/exceptions/package-info.java | 20 + .../org/onosproject/rest/impl/ApiDocManager.java | 73 + .../org/onosproject/rest/impl/package-info.java | 20 + .../onosproject/rest/resources/ApiDocResource.java | 179 + .../rest/resources/ApplicationsWebResource.java | 150 + .../rest/resources/ClusterWebResource.java | 95 + .../rest/resources/ComponentConfigWebResource.java | 118 + .../onosproject/rest/resources/ConfigProvider.java | 610 + .../rest/resources/ConfigWebResource.java | 73 + .../rest/resources/DevicesWebResource.java | 106 + .../rest/resources/FlowsWebResource.java | 190 + .../rest/resources/HostsWebResource.java | 230 + .../rest/resources/IntentsWebResource.java | 221 + .../onosproject/rest/resources/JsonBodyWriter.java | 61 + .../rest/resources/LinksWebResource.java | 102 + .../rest/resources/NetworkConfigWebResource.java | 305 + .../rest/resources/PathsWebResource.java | 85 + .../rest/resources/StatisticsWebResource.java | 95 + .../rest/resources/TopologyWebResource.java | 220 + .../onosproject/rest/resources/package-info.java | 20 + .../web/api/src/main/resources/docs/css/print.css | 1172 + .../web/api/src/main/resources/docs/css/reset.css | 125 + .../web/api/src/main/resources/docs/css/screen.css | 1307 + .../api/src/main/resources/docs/css/typography.css | 0 .../src/main/resources/docs/images/nav-menu.png | Bin 0 -> 1595 bytes .../src/main/resources/docs/images/onos-logo.png | Bin 0 -> 5741 bytes .../web/api/src/main/resources/docs/index.html | 118 + .../src/main/resources/docs/lib/backbone-min.js | 15 + .../main/resources/docs/lib/handlebars-2.0.0.js | 28 + .../main/resources/docs/lib/highlight.7.3.pack.js | 1 + .../main/resources/docs/lib/jquery-1.8.0.min.js | 2 + .../main/resources/docs/lib/jquery.ba-bbq.min.js | 18 + .../main/resources/docs/lib/jquery.slideto.min.js | 1 + .../main/resources/docs/lib/jquery.wiggle.min.js | 8 + .../web/api/src/main/resources/docs/lib/marked.js | 1272 + .../src/main/resources/docs/lib/swagger-oauth.js | 284 + .../src/main/resources/docs/lib/underscore-min.js | 6 + .../web/api/src/main/resources/docs/swagger-ui.js | 32152 +++++++++++++++++++ .../onos/web/api/src/main/webapp/WEB-INF/web.xml | 88 + .../onosproject/rest/ApplicationsResourceTest.java | 332 + .../java/org/onosproject/rest/BadRequestTest.java | 61 + .../rest/ComponentConfigWebResourceTest.java | 128 + .../org/onosproject/rest/DevicesResourceTest.java | 377 + .../org/onosproject/rest/FlowsResourceTest.java | 619 + .../org/onosproject/rest/HostResourceTest.java | 390 + .../org/onosproject/rest/IntentsResourceTest.java | 492 + .../org/onosproject/rest/LinksResourceTest.java | 351 + .../org/onosproject/rest/PathsResourceTest.java | 238 + .../java/org/onosproject/rest/ResourceTest.java | 54 + .../onosproject/rest/StatisticsResourceTest.java | 179 + .../org/onosproject/rest/TopologyResourceTest.java | 281 + .../rest/exceptions/ExceptionMapperTest.java | 35 + .../src/test/java/org/onosproject/rest/topo.json | 19 + .../web/api/src/test/resources/net-config.json | 12 + .../resources/org/onosproject/rest/post-flow.json | 20 + .../resources/org/onosproject/rest/post-host.json | 12 + .../org/onosproject/rest/post-intent.json | 38 + 67 files changed, 43666 insertions(+) create mode 100644 framework/src/onos/web/api/pom.xml create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/AbstractMapper.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/BadRequestMapper.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/EntityNotFoundMapper.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/IllegalArgumentExceptionMapper.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/IllegalStateExceptionMapper.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/NotFoundMapper.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/ServerErrorMapper.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/ServiceNotFoundMapper.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/WebApplicationExceptionMapper.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/package-info.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/impl/ApiDocManager.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/impl/package-info.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ApiDocResource.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ApplicationsWebResource.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ClusterWebResource.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ComponentConfigWebResource.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ConfigProvider.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ConfigWebResource.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/DevicesWebResource.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/FlowsWebResource.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/HostsWebResource.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/IntentsWebResource.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/JsonBodyWriter.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/LinksWebResource.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/NetworkConfigWebResource.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/PathsWebResource.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/StatisticsWebResource.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/TopologyWebResource.java create mode 100644 framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/package-info.java create mode 100644 framework/src/onos/web/api/src/main/resources/docs/css/print.css create mode 100644 framework/src/onos/web/api/src/main/resources/docs/css/reset.css create mode 100644 framework/src/onos/web/api/src/main/resources/docs/css/screen.css create mode 100644 framework/src/onos/web/api/src/main/resources/docs/css/typography.css create mode 100644 framework/src/onos/web/api/src/main/resources/docs/images/nav-menu.png create mode 100644 framework/src/onos/web/api/src/main/resources/docs/images/onos-logo.png create mode 100644 framework/src/onos/web/api/src/main/resources/docs/index.html create mode 100644 framework/src/onos/web/api/src/main/resources/docs/lib/backbone-min.js create mode 100644 framework/src/onos/web/api/src/main/resources/docs/lib/handlebars-2.0.0.js create mode 100644 framework/src/onos/web/api/src/main/resources/docs/lib/highlight.7.3.pack.js create mode 100644 framework/src/onos/web/api/src/main/resources/docs/lib/jquery-1.8.0.min.js create mode 100644 framework/src/onos/web/api/src/main/resources/docs/lib/jquery.ba-bbq.min.js create mode 100644 framework/src/onos/web/api/src/main/resources/docs/lib/jquery.slideto.min.js create mode 100644 framework/src/onos/web/api/src/main/resources/docs/lib/jquery.wiggle.min.js create mode 100644 framework/src/onos/web/api/src/main/resources/docs/lib/marked.js create mode 100644 framework/src/onos/web/api/src/main/resources/docs/lib/swagger-oauth.js create mode 100644 framework/src/onos/web/api/src/main/resources/docs/lib/underscore-min.js create mode 100644 framework/src/onos/web/api/src/main/resources/docs/swagger-ui.js create mode 100644 framework/src/onos/web/api/src/main/webapp/WEB-INF/web.xml create mode 100644 framework/src/onos/web/api/src/test/java/org/onosproject/rest/ApplicationsResourceTest.java create mode 100644 framework/src/onos/web/api/src/test/java/org/onosproject/rest/BadRequestTest.java create mode 100644 framework/src/onos/web/api/src/test/java/org/onosproject/rest/ComponentConfigWebResourceTest.java create mode 100644 framework/src/onos/web/api/src/test/java/org/onosproject/rest/DevicesResourceTest.java create mode 100644 framework/src/onos/web/api/src/test/java/org/onosproject/rest/FlowsResourceTest.java create mode 100644 framework/src/onos/web/api/src/test/java/org/onosproject/rest/HostResourceTest.java create mode 100644 framework/src/onos/web/api/src/test/java/org/onosproject/rest/IntentsResourceTest.java create mode 100644 framework/src/onos/web/api/src/test/java/org/onosproject/rest/LinksResourceTest.java create mode 100644 framework/src/onos/web/api/src/test/java/org/onosproject/rest/PathsResourceTest.java create mode 100644 framework/src/onos/web/api/src/test/java/org/onosproject/rest/ResourceTest.java create mode 100644 framework/src/onos/web/api/src/test/java/org/onosproject/rest/StatisticsResourceTest.java create mode 100644 framework/src/onos/web/api/src/test/java/org/onosproject/rest/TopologyResourceTest.java create mode 100644 framework/src/onos/web/api/src/test/java/org/onosproject/rest/exceptions/ExceptionMapperTest.java create mode 100644 framework/src/onos/web/api/src/test/java/org/onosproject/rest/topo.json create mode 100644 framework/src/onos/web/api/src/test/resources/net-config.json create mode 100644 framework/src/onos/web/api/src/test/resources/org/onosproject/rest/post-flow.json create mode 100644 framework/src/onos/web/api/src/test/resources/org/onosproject/rest/post-host.json create mode 100644 framework/src/onos/web/api/src/test/resources/org/onosproject/rest/post-intent.json (limited to 'framework/src/onos/web/api') diff --git a/framework/src/onos/web/api/pom.xml b/framework/src/onos/web/api/pom.xml new file mode 100644 index 00000000..b4201865 --- /dev/null +++ b/framework/src/onos/web/api/pom.xml @@ -0,0 +1,114 @@ + + + + 4.0.0 + + + org.onosproject + onos-web + 1.3.0-SNAPSHOT + ../pom.xml + + + onos-rest + bundle + + ONOS Core REST API + + + + org.onosproject + onos-core-common + + + org.onosproject + onos-core-common + tests + test + + + org.easymock + easymock + test + + + org.onosproject + onos-api + test + tests + + + + + /onos/v1 + 1.0.0 + ${project.description} + + Core APIs for external interactions with various ONOS subsystems. + + org.onosproject.rest.impl + + + + + + org.onosproject + onos-maven-plugin + + + + org.apache.felix + maven-bundle-plugin + true + + + <_wab>src/main/webapp/ + + WEB-INF/classes/apidoc/swagger.json=target/swagger.json, + {maven-resources} + + + ${project.groupId}.${project.artifactId} + + + org.slf4j, + org.osgi.framework, + javax.ws.rs,javax.ws.rs.core,javax.ws.rs.ext, + com.sun.jersey.api, + com.sun.jersey.spi.container.servlet, + com.sun.jersey.server.impl.container.servlet, + com.fasterxml.jackson.databind, + com.fasterxml.jackson.databind.node, + com.google.common.base.*, + com.google.common.collect.*, + com.google.common.io.*, + org.onlab.util.*, + org.onlab.osgi.*, + org.onlab.packet.*, + org.onlab.rest.*, + org.onosproject.* + + ${web.context} + + + + + + + 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 implements ExceptionMapper { + + /** + * 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 { + @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 { + @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 { + @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 { + @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 { + + @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 { + @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 { + @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 { + + /** + * 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 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 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 = ""; + private static final String INJECT_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(""); + }); + 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 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 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 codec = codec(ControllerNode.class); + ObjectNode root = (ObjectNode) mapper().readTree(config); + String ipPrefix = root.path("ipPrefix").asText(); + + List 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 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 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 connectPoints = Lists.newArrayList(); + private Map 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 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 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 ports = deviceService.getPorts(device.id()); + Set existing = ports.stream() + .map(p -> new ConnectPoint(device.id(), p.number())) + .collect(Collectors.toSet()); + Set missing = connectPoints.stream() + .filter(cp -> cp.deviceId().equals(device.id())) + .filter(cp -> !existing.contains(cp)) + .collect(Collectors.toSet()); + + if (!missing.isEmpty()) { + List 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 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 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 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 devices = get(DeviceService.class).getDevices(); + for (final Device device : devices) { + final Iterable 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 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 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 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 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 ipStrings = node.get("ipAddresses").elements(); + Set 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 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 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 { + + 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 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 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 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 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 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 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 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 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 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 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; diff --git a/framework/src/onos/web/api/src/main/resources/docs/css/print.css b/framework/src/onos/web/api/src/main/resources/docs/css/print.css new file mode 100644 index 00000000..cd3aa8b6 --- /dev/null +++ b/framework/src/onos/web/api/src/main/resources/docs/css/print.css @@ -0,0 +1,1172 @@ +/* Original style from softwaremaniacs.org (c) Ivan Sagalaev */ +.swagger-section pre code { + display: block; + padding: 0.5em; + background: #F0F0F0; +} +.swagger-section pre code, +.swagger-section pre .subst, +.swagger-section pre .tag .title, +.swagger-section pre .lisp .title, +.swagger-section pre .clojure .built_in, +.swagger-section pre .nginx .title { + color: black; +} +.swagger-section pre .string, +.swagger-section pre .title, +.swagger-section pre .constant, +.swagger-section pre .parent, +.swagger-section pre .tag .value, +.swagger-section pre .rules .value, +.swagger-section pre .rules .value .number, +.swagger-section pre .preprocessor, +.swagger-section pre .ruby .symbol, +.swagger-section pre .ruby .symbol .string, +.swagger-section pre .aggregate, +.swagger-section pre .template_tag, +.swagger-section pre .django .variable, +.swagger-section pre .smalltalk .class, +.swagger-section pre .addition, +.swagger-section pre .flow, +.swagger-section pre .stream, +.swagger-section pre .bash .variable, +.swagger-section pre .apache .tag, +.swagger-section pre .apache .cbracket, +.swagger-section pre .tex .command, +.swagger-section pre .tex .special, +.swagger-section pre .erlang_repl .function_or_atom, +.swagger-section pre .markdown .header { + color: #800; +} +.swagger-section pre .comment, +.swagger-section pre .annotation, +.swagger-section pre .template_comment, +.swagger-section pre .diff .header, +.swagger-section pre .chunk, +.swagger-section pre .markdown .blockquote { + color: #888; +} +.swagger-section pre .number, +.swagger-section pre .date, +.swagger-section pre .regexp, +.swagger-section pre .literal, +.swagger-section pre .smalltalk .symbol, +.swagger-section pre .smalltalk .char, +.swagger-section pre .go .constant, +.swagger-section pre .change, +.swagger-section pre .markdown .bullet, +.swagger-section pre .markdown .link_url { + color: #080; +} +.swagger-section pre .label, +.swagger-section pre .javadoc, +.swagger-section pre .ruby .string, +.swagger-section pre .decorator, +.swagger-section pre .filter .argument, +.swagger-section pre .localvars, +.swagger-section pre .array, +.swagger-section pre .attr_selector, +.swagger-section pre .important, +.swagger-section pre .pseudo, +.swagger-section pre .pi, +.swagger-section pre .doctype, +.swagger-section pre .deletion, +.swagger-section pre .envvar, +.swagger-section pre .shebang, +.swagger-section pre .apache .sqbracket, +.swagger-section pre .nginx .built_in, +.swagger-section pre .tex .formula, +.swagger-section pre .erlang_repl .reserved, +.swagger-section pre .prompt, +.swagger-section pre .markdown .link_label, +.swagger-section pre .vhdl .attribute, +.swagger-section pre .clojure .attribute, +.swagger-section pre .coffeescript .property { + color: #8888ff; +} +.swagger-section pre .keyword, +.swagger-section pre .id, +.swagger-section pre .phpdoc, +.swagger-section pre .title, +.swagger-section pre .built_in, +.swagger-section pre .aggregate, +.swagger-section pre .css .tag, +.swagger-section pre .javadoctag, +.swagger-section pre .phpdoc, +.swagger-section pre .yardoctag, +.swagger-section pre .smalltalk .class, +.swagger-section pre .winutils, +.swagger-section pre .bash .variable, +.swagger-section pre .apache .tag, +.swagger-section pre .go .typename, +.swagger-section pre .tex .command, +.swagger-section pre .markdown .strong, +.swagger-section pre .request, +.swagger-section pre .status { + font-weight: bold; +} +.swagger-section pre .markdown .emphasis { + font-style: italic; +} +.swagger-section pre .nginx .built_in { + font-weight: normal; +} +.swagger-section pre .coffeescript .javascript, +.swagger-section pre .javascript .xml, +.swagger-section pre .tex .formula, +.swagger-section pre .xml .javascript, +.swagger-section pre .xml .vbscript, +.swagger-section pre .xml .css, +.swagger-section pre .xml .cdata { + opacity: 0.5; +} +.swagger-section .swagger-ui-wrap { + line-height: 1; + font-family: "Droid Sans", sans-serif; + max-width: 960px; + margin-left: auto; + margin-right: auto; +} +.swagger-section .swagger-ui-wrap b, +.swagger-section .swagger-ui-wrap strong { + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap q, +.swagger-section .swagger-ui-wrap blockquote { + quotes: none; +} +.swagger-section .swagger-ui-wrap p { + line-height: 1.4em; + padding: 0 0 10px; + color: #333333; +} +.swagger-section .swagger-ui-wrap q:before, +.swagger-section .swagger-ui-wrap q:after, +.swagger-section .swagger-ui-wrap blockquote:before, +.swagger-section .swagger-ui-wrap blockquote:after { + content: none; +} +.swagger-section .swagger-ui-wrap .heading_with_menu h1, +.swagger-section .swagger-ui-wrap .heading_with_menu h2, +.swagger-section .swagger-ui-wrap .heading_with_menu h3, +.swagger-section .swagger-ui-wrap .heading_with_menu h4, +.swagger-section .swagger-ui-wrap .heading_with_menu h5, +.swagger-section .swagger-ui-wrap .heading_with_menu h6 { + display: block; + clear: none; + float: left; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + width: 60%; +} +.swagger-section .swagger-ui-wrap table { + border-collapse: collapse; + border-spacing: 0; +} +.swagger-section .swagger-ui-wrap table thead tr th { + padding: 5px; + font-size: 0.9em; + color: #666666; + border-bottom: 1px solid #999999; +} +.swagger-section .swagger-ui-wrap table tbody tr:last-child td { + border-bottom: none; +} +.swagger-section .swagger-ui-wrap table tbody tr.offset { + background-color: #f0f0f0; +} +.swagger-section .swagger-ui-wrap table tbody tr td { + padding: 6px; + font-size: 0.9em; + border-bottom: 1px solid #cccccc; + vertical-align: top; + line-height: 1.3em; +} +.swagger-section .swagger-ui-wrap ol { + margin: 0px 0 10px; + padding: 0 0 0 18px; + list-style-type: decimal; +} +.swagger-section .swagger-ui-wrap ol li { + padding: 5px 0px; + font-size: 0.9em; + color: #333333; +} +.swagger-section .swagger-ui-wrap ol, +.swagger-section .swagger-ui-wrap ul { + list-style: none; +} +.swagger-section .swagger-ui-wrap h1 a, +.swagger-section .swagger-ui-wrap h2 a, +.swagger-section .swagger-ui-wrap h3 a, +.swagger-section .swagger-ui-wrap h4 a, +.swagger-section .swagger-ui-wrap h5 a, +.swagger-section .swagger-ui-wrap h6 a { + text-decoration: none; +} +.swagger-section .swagger-ui-wrap h1 a:hover, +.swagger-section .swagger-ui-wrap h2 a:hover, +.swagger-section .swagger-ui-wrap h3 a:hover, +.swagger-section .swagger-ui-wrap h4 a:hover, +.swagger-section .swagger-ui-wrap h5 a:hover, +.swagger-section .swagger-ui-wrap h6 a:hover { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap h1 span.divider, +.swagger-section .swagger-ui-wrap h2 span.divider, +.swagger-section .swagger-ui-wrap h3 span.divider, +.swagger-section .swagger-ui-wrap h4 span.divider, +.swagger-section .swagger-ui-wrap h5 span.divider, +.swagger-section .swagger-ui-wrap h6 span.divider { + color: #aaaaaa; +} +.swagger-section .swagger-ui-wrap a { + color: #547f00; +} +.swagger-section .swagger-ui-wrap a img { + border: none; +} +.swagger-section .swagger-ui-wrap article, +.swagger-section .swagger-ui-wrap aside, +.swagger-section .swagger-ui-wrap details, +.swagger-section .swagger-ui-wrap figcaption, +.swagger-section .swagger-ui-wrap figure, +.swagger-section .swagger-ui-wrap footer, +.swagger-section .swagger-ui-wrap header, +.swagger-section .swagger-ui-wrap hgroup, +.swagger-section .swagger-ui-wrap menu, +.swagger-section .swagger-ui-wrap nav, +.swagger-section .swagger-ui-wrap section, +.swagger-section .swagger-ui-wrap summary { + display: block; +} +.swagger-section .swagger-ui-wrap pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + padding: 10px; +} +.swagger-section .swagger-ui-wrap pre code { + line-height: 1.6em; + background: none; +} +.swagger-section .swagger-ui-wrap .content > .content-type > div > label { + clear: both; + display: block; + color: #0F6AB4; + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; +} +.swagger-section .swagger-ui-wrap .content pre { + font-size: 12px; + margin-top: 5px; + padding: 5px; +} +.swagger-section .swagger-ui-wrap .icon-btn { + cursor: pointer; +} +.swagger-section .swagger-ui-wrap .info_title { + padding-bottom: 10px; + font-weight: bold; + font-size: 25px; +} +.swagger-section .swagger-ui-wrap .footer { + margin-top: 20px; +} +.swagger-section .swagger-ui-wrap p.big, +.swagger-section .swagger-ui-wrap div.big p { + font-size: 1em; + margin-bottom: 10px; +} +.swagger-section .swagger-ui-wrap form.fullwidth ol li.string input, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.url input, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.text textarea, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.numeric input { + width: 500px !important; +} +.swagger-section .swagger-ui-wrap .info_license { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_tos { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .message-fail { + color: #cc0000; +} +.swagger-section .swagger-ui-wrap .info_url { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_email { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_name { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_description { + padding-bottom: 10px; + font-size: 15px; +} +.swagger-section .swagger-ui-wrap .markdown ol li, +.swagger-section .swagger-ui-wrap .markdown ul li { + padding: 3px 0px; + line-height: 1.4em; + color: #333333; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input { + display: block; + padding: 4px; + width: auto; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input.title, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input.title, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input.title { + font-size: 1.3em; +} +.swagger-section .swagger-ui-wrap table.fullwidth { + width: 100%; +} +.swagger-section .swagger-ui-wrap .model-signature { + font-family: "Droid Sans", sans-serif; + font-size: 1em; + line-height: 1.5em; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav a { + text-decoration: none; + color: #AAA; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav a:hover { + text-decoration: underline; + color: black; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav .selected { + color: black; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap .model-signature .propType { + color: #5555aa; +} +.swagger-section .swagger-ui-wrap .model-signature pre:hover { + background-color: #ffffdd; +} +.swagger-section .swagger-ui-wrap .model-signature pre { + font-size: .85em; + line-height: 1.2em; + overflow: auto; + max-height: 200px; + cursor: pointer; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav { + display: block; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li:last-child { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li { + float: left; + margin: 0 5px 5px 0; + padding: 2px 5px 2px 0; + border-right: 1px solid #ddd; +} +.swagger-section .swagger-ui-wrap .model-signature .propOpt { + color: #555; +} +.swagger-section .swagger-ui-wrap .model-signature .snippet small { + font-size: 0.75em; +} +.swagger-section .swagger-ui-wrap .model-signature .propOptKey { + font-style: italic; +} +.swagger-section .swagger-ui-wrap .model-signature .description .strong { + font-weight: bold; + color: #000; + font-size: .9em; +} +.swagger-section .swagger-ui-wrap .model-signature .description div { + font-size: 0.9em; + line-height: 1.5em; + margin-left: 1em; +} +.swagger-section .swagger-ui-wrap .model-signature .description .stronger { + font-weight: bold; + color: #000; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper { + border-spacing: 0; + position: absolute; + background-color: #ffffff; + border: 1px solid #bbbbbb; + display: none; + font-size: 11px; + max-width: 400px; + line-height: 30px; + color: black; + padding: 5px; + margin-left: 10px; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper th { + text-align: center; + background-color: #eeeeee; + border: 1px solid #bbbbbb; + font-size: 11px; + color: #666666; + font-weight: bold; + padding: 5px; + line-height: 15px; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper .optionName { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:first-child, +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:last-child { + display: inline; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:not(:first-child):before { + display: block; + content: ''; +} +.swagger-section .swagger-ui-wrap .model-signature .description span:last-of-type.propDesc.markdown > p:only-child { + margin-right: -3px; +} +.swagger-section .swagger-ui-wrap .model-signature .propName { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-container { + clear: both; +} +.swagger-section .swagger-ui-wrap .body-textarea { + width: 300px; + height: 100px; + border: 1px solid #aaa; +} +.swagger-section .swagger-ui-wrap .markdown p code, +.swagger-section .swagger-ui-wrap .markdown li code { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #f0f0f0; + color: black; + padding: 1px 3px; +} +.swagger-section .swagger-ui-wrap .required { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap input.parameter { + width: 300px; + border: 1px solid #aaa; +} +.swagger-section .swagger-ui-wrap h1 { + color: black; + font-size: 1.5em; + line-height: 1.3em; + padding: 10px 0 10px 0; + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .heading_with_menu { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap .heading_with_menu ul { + display: block; + clear: none; + float: right; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + margin-top: 10px; +} +.swagger-section .swagger-ui-wrap h2 { + color: black; + font-size: 1.3em; + padding: 10px 0 10px 0; +} +.swagger-section .swagger-ui-wrap h2 a { + color: black; +} +.swagger-section .swagger-ui-wrap h2 span.sub { + font-size: 0.7em; + color: #999999; + font-style: italic; +} +.swagger-section .swagger-ui-wrap h2 span.sub a { + color: #777777; +} +.swagger-section .swagger-ui-wrap span.weak { + color: #666666; +} +.swagger-section .swagger-ui-wrap .message-success { + color: #89BF04; +} +.swagger-section .swagger-ui-wrap caption, +.swagger-section .swagger-ui-wrap th, +.swagger-section .swagger-ui-wrap td { + text-align: left; + font-weight: normal; + vertical-align: middle; +} +.swagger-section .swagger-ui-wrap .code { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.text textarea { + font-family: "Droid Sans", sans-serif; + height: 250px; + padding: 4px; + display: block; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.select select { + display: block; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean label { + display: block; + float: left; + clear: none; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean input { + display: block; + float: left; + clear: none; + margin: 0 5px 0 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.required label { + color: black; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label { + display: block; + clear: both; + width: auto; + padding: 0 0 3px; + color: #666666; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label abbr { + padding-left: 3px; + color: #888888; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li p.inline-hints { + margin-left: 0; + font-style: italic; + font-size: 0.9em; + margin: 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.buttons { + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap span.blank, +.swagger-section .swagger-ui-wrap span.empty { + color: #888888; + font-style: italic; +} +.swagger-section .swagger-ui-wrap .markdown h3 { + color: #547f00; +} +.swagger-section .swagger-ui-wrap .markdown h4 { + color: #666666; +} +.swagger-section .swagger-ui-wrap .markdown pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + padding: 10px; + margin: 0 0 10px 0; +} +.swagger-section .swagger-ui-wrap .markdown pre code { + line-height: 1.6em; +} +.swagger-section .swagger-ui-wrap div.gist { + margin: 20px 0 25px 0 !important; +} +.swagger-section .swagger-ui-wrap ul#resources { + font-family: "Droid Sans", sans-serif; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource { + border-bottom: 1px solid #dddddd; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading h2 a, +.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading h2 a { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading ul.options li a, +.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading ul.options li a { + color: #555555; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:last-child { + border-bottom: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading { + border: 1px solid transparent; + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options { + overflow: hidden; + padding: 0; + display: block; + clear: none; + float: right; + margin: 14px 10px 0 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li { + float: left; + clear: none; + margin: 0; + padding: 2px 10px; + border-right: 1px solid #dddddd; + color: #666666; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a { + color: #aaaaaa; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover { + text-decoration: underline; + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:active, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a.active { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.last { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 { + color: #999999; + padding-left: 0; + display: block; + clear: none; + float: left; + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a { + color: #999999; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation { + float: none; + clear: both; + overflow: hidden; + display: block; + margin: 0 0 10px; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading { + float: none; + clear: both; + overflow: hidden; + display: block; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 { + display: block; + clear: none; + float: left; + width: auto; + margin: 0; + padding: 0; + line-height: 1.1em; + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path { + padding-left: 10px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a { + color: black; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a:hover { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.http_method a { + text-transform: uppercase; + text-decoration: none; + color: white; + display: inline-block; + width: 50px; + font-size: 0.7em; + text-align: center; + padding: 7px 0 4px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + -o-border-radius: 2px; + -ms-border-radius: 2px; + -khtml-border-radius: 2px; + border-radius: 2px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span { + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options { + overflow: hidden; + padding: 0; + display: block; + clear: none; + float: right; + margin: 6px 10px 0 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li { + float: left; + clear: none; + margin: 0; + padding: 2px 10px; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li a { + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li.access { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content { + border-top: none; + padding: 10px; + -moz-border-radius-bottomleft: 6px; + -webkit-border-bottom-left-radius: 6px; + -o-border-bottom-left-radius: 6px; + -ms-border-bottom-left-radius: 6px; + -khtml-border-bottom-left-radius: 6px; + border-bottom-left-radius: 6px; + -moz-border-radius-bottomright: 6px; + -webkit-border-bottom-right-radius: 6px; + -o-border-bottom-right-radius: 6px; + -ms-border-bottom-right-radius: 6px; + -khtml-border-bottom-right-radius: 6px; + border-bottom-right-radius: 6px; + margin: 0 0 20px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content h4 { + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header a { + padding: 4px 0 0 10px; + display: inline-block; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header input.submit { + display: block; + clear: none; + float: left; + padding: 6px 8px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header span.response_throbber { + background-image: url('../images/throbber.gif'); + width: 128px; + height: 16px; + display: block; + clear: none; + float: right; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form input[type='text'].error { + outline: 2px solid black; + outline-color: #cc0000; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.response div.block pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + padding: 10px; + font-size: 0.9em; + max-height: 400px; + overflow-y: auto; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading { + background-color: #f9f2e9; + border: 1px solid #f0e0ca; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading h3 span.http_method a { + background-color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #f0e0ca; + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li a { + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content { + background-color: #faf5ee; + border: 1px solid #f0e0ca; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content h4 { + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content div.sandbox_header a { + color: #dcb67f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading { + background-color: #fcffcd; + border: 1px solid black; + border-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading h3 span.http_method a { + text-transform: uppercase; + background-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #ffd20f; + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li a { + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content { + background-color: #fcffcd; + border: 1px solid black; + border-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content h4 { + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content div.sandbox_header a { + color: #6fc992; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading { + background-color: #f5e8e8; + border: 1px solid #e8c6c7; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading h3 span.http_method a { + text-transform: uppercase; + background-color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #e8c6c7; + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li a { + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content { + background-color: #f7eded; + border: 1px solid #e8c6c7; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content h4 { + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content div.sandbox_header a { + color: #c8787a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading { + background-color: #e7f6ec; + border: 1px solid #c3e8d1; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading h3 span.http_method a { + background-color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3e8d1; + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li a { + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content { + background-color: #ebf7f0; + border: 1px solid #c3e8d1; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content h4 { + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content div.sandbox_header a { + color: #6fc992; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading { + background-color: #FCE9E3; + border: 1px solid #F5D5C3; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading h3 span.http_method a { + background-color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #f0cecb; + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li a { + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content { + background-color: #faf0ef; + border: 1px solid #f0cecb; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content h4 { + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content div.sandbox_header a { + color: #dcb67f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading { + background-color: #e7f0f7; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading h3 span.http_method a { + background-color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3d9ec; + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li a { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content h4 { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content div.sandbox_header a { + color: #6fa5d2; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading { + background-color: #e7f0f7; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading h3 span.http_method a { + background-color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3d9ec; + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li a { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content h4 { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content div.sandbox_header a { + color: #6fa5d2; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content { + border-top: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li.last { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:hover, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:active, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a.active { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap p#colophon { + margin: 0 15px 40px 15px; + padding: 10px 0; + font-size: 0.8em; + border-top: 1px solid #dddddd; + font-family: "Droid Sans", sans-serif; + color: #999999; + font-style: italic; +} +.swagger-section .swagger-ui-wrap p#colophon a { + text-decoration: none; + color: #547f00; +} +.swagger-section .swagger-ui-wrap h3 { + color: black; + font-size: 1.1em; + padding: 10px 0 10px 0; +} +.swagger-section .swagger-ui-wrap .markdown ol, +.swagger-section .swagger-ui-wrap .markdown ul { + font-family: "Droid Sans", sans-serif; + margin: 5px 0 10px; + padding: 0 0 0 18px; + list-style-type: disc; +} +.swagger-section .swagger-ui-wrap form.form_box { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; + padding: 10px; +} +.swagger-section .swagger-ui-wrap form.form_box label { + color: #0f6ab4 !important; +} +.swagger-section .swagger-ui-wrap form.form_box input[type=submit] { + display: block; + padding: 10px; +} +.swagger-section .swagger-ui-wrap form.form_box p.weak { + font-size: 0.8em; +} +.swagger-section .swagger-ui-wrap form.form_box p { + font-size: 0.9em; + padding: 0 0 15px; + color: #7e7b6d; +} +.swagger-section .swagger-ui-wrap form.form_box p a { + color: #646257; +} +.swagger-section .swagger-ui-wrap form.form_box p strong { + color: black; +} +.swagger-section .swagger-ui-wrap .operation-status td.markdown > p:last-child { + padding-bottom: 0; +} +.swagger-section .title { + font-style: bold; +} +.swagger-section .secondary_form { + display: none; +} +.swagger-section .main_image { + display: block; + margin-left: auto; + margin-right: auto; +} +.swagger-section .oauth_body { + margin-left: 100px; + margin-right: 100px; +} +.swagger-section .oauth_submit { + text-align: center; +} +.swagger-section .api-popup-dialog { + z-index: 10000; + position: absolute; + width: 500px; + background: #FFF; + padding: 20px; + border: 1px solid #ccc; + border-radius: 5px; + display: none; + font-size: 13px; + color: #777; +} +.swagger-section .api-popup-dialog .api-popup-title { + font-size: 24px; + padding: 10px 0; +} +.swagger-section .api-popup-dialog .api-popup-title { + font-size: 24px; + padding: 10px 0; +} +.swagger-section .api-popup-dialog p.error-msg { + padding-left: 5px; + padding-bottom: 5px; +} +.swagger-section .api-popup-dialog button.api-popup-authbtn { + height: 30px; +} +.swagger-section .api-popup-dialog button.api-popup-cancel { + height: 30px; +} +.swagger-section .api-popup-scopes { + padding: 10px 20px; +} +.swagger-section .api-popup-scopes li { + padding: 5px 0; + line-height: 20px; +} +.swagger-section .api-popup-scopes .api-scope-desc { + padding-left: 20px; + font-style: italic; +} +.swagger-section .api-popup-scopes li input { + position: relative; + top: 2px; +} +.swagger-section .api-popup-actions { + padding-top: 10px; +} +#header { + display: none; +} +.swagger-section .swagger-ui-wrap .model-signature pre { + max-height: none; +} +.swagger-section .swagger-ui-wrap .body-textarea { + width: 100px; +} +.swagger-section .swagger-ui-wrap input.parameter { + width: 100px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options { + display: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints { + display: block !important; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content { + display: block !important; +} diff --git a/framework/src/onos/web/api/src/main/resources/docs/css/reset.css b/framework/src/onos/web/api/src/main/resources/docs/css/reset.css new file mode 100644 index 00000000..b2b07894 --- /dev/null +++ b/framework/src/onos/web/api/src/main/resources/docs/css/reset.css @@ -0,0 +1,125 @@ +/* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 */ +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} +body { + line-height: 1; +} +ol, +ul { + list-style: none; +} +blockquote, +q { + quotes: none; +} +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/framework/src/onos/web/api/src/main/resources/docs/css/screen.css b/framework/src/onos/web/api/src/main/resources/docs/css/screen.css new file mode 100644 index 00000000..5586d968 --- /dev/null +++ b/framework/src/onos/web/api/src/main/resources/docs/css/screen.css @@ -0,0 +1,1307 @@ +/* Original style from softwaremaniacs.org (c) Ivan Sagalaev */ +.swagger-section pre code { + display: block; + padding: 0.5em; + background: #F0F0F0; +} +.swagger-section pre code, +.swagger-section pre .subst, +.swagger-section pre .tag .title, +.swagger-section pre .lisp .title, +.swagger-section pre .clojure .built_in, +.swagger-section pre .nginx .title { + color: black; +} +.swagger-section pre .string, +.swagger-section pre .title, +.swagger-section pre .constant, +.swagger-section pre .parent, +.swagger-section pre .tag .value, +.swagger-section pre .rules .value, +.swagger-section pre .rules .value .number, +.swagger-section pre .preprocessor, +.swagger-section pre .ruby .symbol, +.swagger-section pre .ruby .symbol .string, +.swagger-section pre .aggregate, +.swagger-section pre .template_tag, +.swagger-section pre .django .variable, +.swagger-section pre .smalltalk .class, +.swagger-section pre .addition, +.swagger-section pre .flow, +.swagger-section pre .stream, +.swagger-section pre .bash .variable, +.swagger-section pre .apache .tag, +.swagger-section pre .apache .cbracket, +.swagger-section pre .tex .command, +.swagger-section pre .tex .special, +.swagger-section pre .erlang_repl .function_or_atom, +.swagger-section pre .markdown .header { + color: #800; +} +.swagger-section pre .comment, +.swagger-section pre .annotation, +.swagger-section pre .template_comment, +.swagger-section pre .diff .header, +.swagger-section pre .chunk, +.swagger-section pre .markdown .blockquote { + color: #888; +} +.swagger-section pre .number, +.swagger-section pre .date, +.swagger-section pre .regexp, +.swagger-section pre .literal, +.swagger-section pre .smalltalk .symbol, +.swagger-section pre .smalltalk .char, +.swagger-section pre .go .constant, +.swagger-section pre .change, +.swagger-section pre .markdown .bullet, +.swagger-section pre .markdown .link_url { + color: #080; +} +.swagger-section pre .label, +.swagger-section pre .javadoc, +.swagger-section pre .ruby .string, +.swagger-section pre .decorator, +.swagger-section pre .filter .argument, +.swagger-section pre .localvars, +.swagger-section pre .array, +.swagger-section pre .attr_selector, +.swagger-section pre .important, +.swagger-section pre .pseudo, +.swagger-section pre .pi, +.swagger-section pre .doctype, +.swagger-section pre .deletion, +.swagger-section pre .envvar, +.swagger-section pre .shebang, +.swagger-section pre .apache .sqbracket, +.swagger-section pre .nginx .built_in, +.swagger-section pre .tex .formula, +.swagger-section pre .erlang_repl .reserved, +.swagger-section pre .prompt, +.swagger-section pre .markdown .link_label, +.swagger-section pre .vhdl .attribute, +.swagger-section pre .clojure .attribute, +.swagger-section pre .coffeescript .property { + color: #8888ff; +} +.swagger-section pre .keyword, +.swagger-section pre .id, +.swagger-section pre .phpdoc, +.swagger-section pre .title, +.swagger-section pre .built_in, +.swagger-section pre .aggregate, +.swagger-section pre .css .tag, +.swagger-section pre .javadoctag, +.swagger-section pre .phpdoc, +.swagger-section pre .yardoctag, +.swagger-section pre .smalltalk .class, +.swagger-section pre .winutils, +.swagger-section pre .bash .variable, +.swagger-section pre .apache .tag, +.swagger-section pre .go .typename, +.swagger-section pre .tex .command, +.swagger-section pre .markdown .strong, +.swagger-section pre .request, +.swagger-section pre .status { + font-weight: bold; +} +.swagger-section pre .markdown .emphasis { + font-style: italic; +} +.swagger-section pre .nginx .built_in { + font-weight: normal; +} +.swagger-section pre .coffeescript .javascript, +.swagger-section pre .javascript .xml, +.swagger-section pre .tex .formula, +.swagger-section pre .xml .javascript, +.swagger-section pre .xml .vbscript, +.swagger-section pre .xml .css, +.swagger-section pre .xml .cdata { + opacity: 0.5; +} +.swagger-section .swagger-ui-wrap { + line-height: 1; + font-family: Helvetica, Arial, sans-serif; + max-width: 960px; + margin-left: auto; + margin-right: auto; +} +.swagger-section .swagger-ui-wrap b, +.swagger-section .swagger-ui-wrap strong { + font-family: Helvetica, Arial, sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap q, +.swagger-section .swagger-ui-wrap blockquote { + quotes: none; +} +.swagger-section .swagger-ui-wrap p { + line-height: 1.4em; + padding: 0 0 10px; + color: #333333; +} +.swagger-section .swagger-ui-wrap q:before, +.swagger-section .swagger-ui-wrap q:after, +.swagger-section .swagger-ui-wrap blockquote:before, +.swagger-section .swagger-ui-wrap blockquote:after { + content: none; +} +.swagger-section .swagger-ui-wrap .heading_with_menu h1, +.swagger-section .swagger-ui-wrap .heading_with_menu h2, +.swagger-section .swagger-ui-wrap .heading_with_menu h3, +.swagger-section .swagger-ui-wrap .heading_with_menu h4, +.swagger-section .swagger-ui-wrap .heading_with_menu h5, +.swagger-section .swagger-ui-wrap .heading_with_menu h6 { + display: block; + clear: none; + float: left; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + width: 60%; +} +.swagger-section .swagger-ui-wrap table { + border-collapse: collapse; + border-spacing: 0; +} +.swagger-section .swagger-ui-wrap table thead tr th { + padding: 5px; + font-size: 0.9em; + color: #666666; + border-bottom: 1px solid #999999; +} +.swagger-section .swagger-ui-wrap table tbody tr:last-child td { + border-bottom: none; +} +.swagger-section .swagger-ui-wrap table tbody tr.offset { + background-color: #f0f0f0; +} +.swagger-section .swagger-ui-wrap table tbody tr td { + padding: 6px; + font-size: 0.9em; + border-bottom: 1px solid #cccccc; + vertical-align: top; + line-height: 1.3em; +} +.swagger-section .swagger-ui-wrap ol { + margin: 0px 0 10px; + padding: 0 0 0 18px; + list-style-type: decimal; +} +.swagger-section .swagger-ui-wrap ol li { + padding: 5px 0px; + font-size: 0.9em; + color: #333333; +} +.swagger-section .swagger-ui-wrap ol, +.swagger-section .swagger-ui-wrap ul { + list-style: none; +} +.swagger-section .swagger-ui-wrap h1 a, +.swagger-section .swagger-ui-wrap h2 a, +.swagger-section .swagger-ui-wrap h3 a, +.swagger-section .swagger-ui-wrap h4 a, +.swagger-section .swagger-ui-wrap h5 a, +.swagger-section .swagger-ui-wrap h6 a { + text-decoration: none; +} +.swagger-section .swagger-ui-wrap h1 a:hover, +.swagger-section .swagger-ui-wrap h2 a:hover, +.swagger-section .swagger-ui-wrap h3 a:hover, +.swagger-section .swagger-ui-wrap h4 a:hover, +.swagger-section .swagger-ui-wrap h5 a:hover, +.swagger-section .swagger-ui-wrap h6 a:hover { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap h1 span.divider, +.swagger-section .swagger-ui-wrap h2 span.divider, +.swagger-section .swagger-ui-wrap h3 span.divider, +.swagger-section .swagger-ui-wrap h4 span.divider, +.swagger-section .swagger-ui-wrap h5 span.divider, +.swagger-section .swagger-ui-wrap h6 span.divider { + color: #aaaaaa; +} +.swagger-section .swagger-ui-wrap a { + color: #CE5650; +} +.swagger-section .swagger-ui-wrap a img { + border: none; +} +.swagger-section .swagger-ui-wrap article, +.swagger-section .swagger-ui-wrap aside, +.swagger-section .swagger-ui-wrap details, +.swagger-section .swagger-ui-wrap figcaption, +.swagger-section .swagger-ui-wrap figure, +.swagger-section .swagger-ui-wrap footer, +.swagger-section .swagger-ui-wrap header, +.swagger-section .swagger-ui-wrap hgroup, +.swagger-section .swagger-ui-wrap menu, +.swagger-section .swagger-ui-wrap nav, +.swagger-section .swagger-ui-wrap section, +.swagger-section .swagger-ui-wrap summary { + display: block; +} +.swagger-section .swagger-ui-wrap pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + padding: 10px; +} +.swagger-section .swagger-ui-wrap pre code { + line-height: 1.6em; + background: none; +} +.swagger-section .swagger-ui-wrap .content > .content-type > div > label { + clear: both; + display: block; + color: #0F6AB4; + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; +} +.swagger-section .swagger-ui-wrap .content pre { + font-size: 12px; + margin-top: 5px; + padding: 5px; +} +.swagger-section .swagger-ui-wrap .icon-btn { + cursor: pointer; +} +.swagger-section .swagger-ui-wrap .info_title { + padding-bottom: 10px; + font-weight: bold; + font-size: 25px; +} +.swagger-section .swagger-ui-wrap .footer { + margin-top: 20px; +} +.swagger-section .swagger-ui-wrap p.big, +.swagger-section .swagger-ui-wrap div.big p { + font-size: 1em; + margin-bottom: 10px; +} +.swagger-section .swagger-ui-wrap form.fullwidth ol li.string input, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.url input, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.text textarea, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.numeric input { + width: 500px !important; +} +.swagger-section .swagger-ui-wrap .info_license { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_tos { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .message-fail { + color: #cc0000; +} +.swagger-section .swagger-ui-wrap .info_url { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_email { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_name { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_description { + padding-bottom: 10px; + font-size: 15px; +} +.swagger-section .swagger-ui-wrap .markdown ol li, +.swagger-section .swagger-ui-wrap .markdown ul li { + padding: 3px 0px; + line-height: 1.4em; + color: #333333; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input { + display: block; + padding: 4px; + width: auto; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input.title, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input.title, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input.title { + font-size: 1.3em; +} +.swagger-section .swagger-ui-wrap table.fullwidth { + width: 100%; +} +.swagger-section .swagger-ui-wrap .model-signature { + font-family: Helvetica, Arial, sans-serif; + font-size: 1em; + line-height: 1.5em; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav a { + text-decoration: none; + color: #AAA; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav a:hover { + text-decoration: underline; + color: black; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav .selected { + color: black; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap .model-signature .propType { + color: #5555aa; +} +.swagger-section .swagger-ui-wrap .model-signature pre:hover { + background-color: #ffffdd; +} +.swagger-section .swagger-ui-wrap .model-signature pre { + font-size: .85em; + line-height: 1.2em; + overflow: auto; + max-height: 200px; + cursor: pointer; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav { + display: block; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li:last-child { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li { + float: left; + margin: 0 5px 5px 0; + padding: 2px 5px 2px 0; + border-right: 1px solid #ddd; +} +.swagger-section .swagger-ui-wrap .model-signature .propOpt { + color: #555; +} +.swagger-section .swagger-ui-wrap .model-signature .snippet small { + font-size: 0.75em; +} +.swagger-section .swagger-ui-wrap .model-signature .propOptKey { + font-style: italic; +} +.swagger-section .swagger-ui-wrap .model-signature .description .strong { + font-weight: bold; + color: #000; + font-size: .9em; +} +.swagger-section .swagger-ui-wrap .model-signature .description div { + font-size: 0.9em; + line-height: 1.5em; + margin-left: 1em; +} +.swagger-section .swagger-ui-wrap .model-signature .description .stronger { + font-weight: bold; + color: #000; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper { + border-spacing: 0; + position: absolute; + background-color: #ffffff; + border: 1px solid #bbbbbb; + display: none; + font-size: 11px; + max-width: 400px; + line-height: 30px; + color: black; + padding: 5px; + margin-left: 10px; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper th { + text-align: center; + background-color: #eeeeee; + border: 1px solid #bbbbbb; + font-size: 11px; + color: #666666; + font-weight: bold; + padding: 5px; + line-height: 15px; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper .optionName { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:first-child, +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:last-child { + display: inline; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propDesc.markdown > p:not(:first-child):before { + display: block; + content: ''; +} +.swagger-section .swagger-ui-wrap .model-signature .description span:last-of-type.propDesc.markdown > p:only-child { + margin-right: -3px; +} +.swagger-section .swagger-ui-wrap .model-signature .propName { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-container { + clear: both; +} +.swagger-section .swagger-ui-wrap .body-textarea { + width: 300px; + height: 100px; + border: 1px solid #aaa; +} +.swagger-section .swagger-ui-wrap .markdown p code, +.swagger-section .swagger-ui-wrap .markdown li code { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #f0f0f0; + color: black; + padding: 1px 3px; +} +.swagger-section .swagger-ui-wrap .required { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap input.parameter { + width: 300px; + border: 1px solid #aaa; +} +.swagger-section .swagger-ui-wrap h1 { + color: black; + font-size: 1.5em; + line-height: 1.3em; + padding: 10px 0 10px 0; + font-family: Helvetica, Arial, sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .heading_with_menu { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap .heading_with_menu ul { + display: block; + clear: none; + float: right; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + margin-top: 10px; +} +.swagger-section .swagger-ui-wrap h2 { + color: black; + font-size: 1.3em; + padding: 10px 0 10px 0; +} +.swagger-section .swagger-ui-wrap h2 a { + color: black; +} +.swagger-section .swagger-ui-wrap h2 span.sub { + font-size: 0.7em; + color: #999999; + font-style: italic; +} +.swagger-section .swagger-ui-wrap h2 span.sub a { + color: #777777; +} +.swagger-section .swagger-ui-wrap span.weak { + color: #666666; +} +.swagger-section .swagger-ui-wrap .message-success { + color: #89BF04; +} +.swagger-section .swagger-ui-wrap caption, +.swagger-section .swagger-ui-wrap th, +.swagger-section .swagger-ui-wrap td { + text-align: left; + font-weight: normal; + vertical-align: middle; +} +.swagger-section .swagger-ui-wrap .code { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.text textarea { + font-family: Helvetica, Arial, sans-serif; + height: 250px; + padding: 4px; + display: block; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.select select { + display: block; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean label { + display: block; + float: left; + clear: none; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean input { + display: block; + float: left; + clear: none; + margin: 0 5px 0 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.required label { + color: black; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label { + display: block; + clear: both; + width: auto; + padding: 0 0 3px; + color: #666666; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label abbr { + padding-left: 3px; + color: #888888; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li p.inline-hints { + margin-left: 0; + font-style: italic; + font-size: 0.9em; + margin: 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.buttons { + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap span.blank, +.swagger-section .swagger-ui-wrap span.empty { + color: #888888; + font-style: italic; +} +.swagger-section .swagger-ui-wrap .markdown h3 { + color: #CE5650; +} +.swagger-section .swagger-ui-wrap .markdown h4 { + color: #666666; +} +.swagger-section .swagger-ui-wrap .markdown pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + padding: 10px; + margin: 0 0 10px 0; +} +.swagger-section .swagger-ui-wrap .markdown pre code { + line-height: 1.6em; +} +.swagger-section .swagger-ui-wrap div.gist { + margin: 20px 0 25px 0 !important; +} +.swagger-section .swagger-ui-wrap ul#resources { + font-family: Helvetica, Arial, sans-serif; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource { + border-bottom: 1px solid #dddddd; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading h2 a, +.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading h2 a { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading ul.options li a, +.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading ul.options li a { + color: #555555; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:last-child { + border-bottom: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading { + border: 1px solid transparent; + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options { + overflow: hidden; + padding: 0; + display: block; + clear: none; + float: right; + margin: 14px 10px 0 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li { + float: left; + clear: none; + margin: 0; + padding: 2px 10px; + border-right: 1px solid #dddddd; + color: #666666; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a { + color: #aaaaaa; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover { + text-decoration: underline; + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:active, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a.active { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.last { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 { + color: #999999; + padding-left: 0; + display: block; + clear: none; + float: left; + font-family: Helvetica, Arial, sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a { + color: #999999; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation { + float: none; + clear: both; + overflow: hidden; + display: block; + margin: 0 0 10px; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading { + float: none; + clear: both; + overflow: hidden; + display: block; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 { + display: block; + clear: none; + float: left; + width: auto; + margin: 0; + padding: 0; + line-height: 1.1em; + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path { + padding-left: 10px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a { + color: black; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a:hover { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.http_method a { + text-transform: uppercase; + text-decoration: none; + color: white; + display: inline-block; + width: 50px; + font-size: 0.7em; + text-align: center; + padding: 7px 0 4px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + -o-border-radius: 2px; + -ms-border-radius: 2px; + -khtml-border-radius: 2px; + border-radius: 2px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span { + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options { + overflow: hidden; + padding: 0; + display: block; + clear: none; + float: right; + margin: 6px 10px 0 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li { + float: left; + clear: none; + margin: 0; + padding: 2px 10px; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li a { + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li.access { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content { + border-top: none; + padding: 10px; + -moz-border-radius-bottomleft: 6px; + -webkit-border-bottom-left-radius: 6px; + -o-border-bottom-left-radius: 6px; + -ms-border-bottom-left-radius: 6px; + -khtml-border-bottom-left-radius: 6px; + border-bottom-left-radius: 6px; + -moz-border-radius-bottomright: 6px; + -webkit-border-bottom-right-radius: 6px; + -o-border-bottom-right-radius: 6px; + -ms-border-bottom-right-radius: 6px; + -khtml-border-bottom-right-radius: 6px; + border-bottom-right-radius: 6px; + margin: 0 0 20px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content h4 { + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header a { + padding: 4px 0 0 10px; + display: inline-block; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header input.submit { + display: block; + clear: none; + float: left; + padding: 6px 8px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header span.response_throbber { + background-image: url('../images/throbber.gif'); + width: 128px; + height: 16px; + display: block; + clear: none; + float: right; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form input[type='text'].error { + outline: 2px solid black; + outline-color: #cc0000; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.response div.block pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + padding: 10px; + font-size: 0.9em; + max-height: 400px; + overflow-y: auto; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading { + background-color: #f9f2e9; + border: 1px solid #f0e0ca; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading h3 span.http_method a { + background-color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #f0e0ca; + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li a { + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content { + background-color: #faf5ee; + border: 1px solid #f0e0ca; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content h4 { + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content div.sandbox_header a { + color: #dcb67f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading { + background-color: #fcffcd; + border: 1px solid black; + border-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading h3 span.http_method a { + text-transform: uppercase; + background-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #ffd20f; + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li a { + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content { + background-color: #fcffcd; + border: 1px solid black; + border-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content h4 { + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content div.sandbox_header a { + color: #6fc992; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading { + background-color: #f5e8e8; + border: 1px solid #e8c6c7; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading h3 span.http_method a { + text-transform: uppercase; + background-color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #e8c6c7; + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li a { + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content { + background-color: #f7eded; + border: 1px solid #e8c6c7; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content h4 { + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content div.sandbox_header a { + color: #c8787a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading { + background-color: #e7f6ec; + border: 1px solid #c3e8d1; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading h3 span.http_method a { + background-color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3e8d1; + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li a { + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content { + background-color: #ebf7f0; + border: 1px solid #c3e8d1; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content h4 { + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content div.sandbox_header a { + color: #6fc992; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading { + background-color: #FCE9E3; + border: 1px solid #F5D5C3; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading h3 span.http_method a { + background-color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #f0cecb; + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li a { + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content { + background-color: #faf0ef; + border: 1px solid #f0cecb; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content h4 { + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content div.sandbox_header a { + color: #dcb67f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading { + background-color: #e7f0f7; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading h3 span.http_method a { + background-color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3d9ec; + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li a { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content h4 { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content div.sandbox_header a { + color: #6fa5d2; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading { + background-color: #e7f0f7; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading h3 span.http_method a { + background-color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3d9ec; + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li a { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content h4 { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content div.sandbox_header a { + color: #6fa5d2; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content { + border-top: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li.last { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:hover, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:active, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a.active { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap p#colophon { + margin: 0 15px 40px 15px; + padding: 10px 0; + font-size: 0.8em; + border-top: 1px solid #dddddd; + font-family: Helvetica, Arial, sans-serif; + color: #999999; + font-style: italic; +} +.swagger-section .swagger-ui-wrap p#colophon a { + text-decoration: none; + color: #CE5650; +} +.swagger-section .swagger-ui-wrap h3 { + color: black; + font-size: 1.1em; + padding: 10px 0 10px 0; +} +.swagger-section .swagger-ui-wrap .markdown ol, +.swagger-section .swagger-ui-wrap .markdown ul { + font-family: Helvetica, Arial, sans-serif; + margin: 5px 0 10px; + padding: 0 0 0 18px; + list-style-type: disc; +} +.swagger-section .swagger-ui-wrap form.form_box { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; + padding: 10px; +} +.swagger-section .swagger-ui-wrap form.form_box label { + color: #0f6ab4 !important; +} +.swagger-section .swagger-ui-wrap form.form_box input[type=submit] { + display: block; + padding: 10px; +} +.swagger-section .swagger-ui-wrap form.form_box p.weak { + font-size: 0.8em; +} +.swagger-section .swagger-ui-wrap form.form_box p { + font-size: 0.9em; + padding: 0 0 15px; + color: #7e7b6d; +} +.swagger-section .swagger-ui-wrap form.form_box p a { + color: #646257; +} +.swagger-section .swagger-ui-wrap form.form_box p strong { + color: black; +} +.swagger-section .swagger-ui-wrap .operation-status td.markdown > p:last-child { + padding-bottom: 0; +} +.swagger-section .title { + font-style: bold; +} +.swagger-section .secondary_form { + display: none; +} +.swagger-section .main_image { + display: block; + margin-left: auto; + margin-right: auto; +} +.swagger-section .oauth_body { + margin-left: 100px; + margin-right: 100px; +} +.swagger-section .oauth_submit { + text-align: center; +} +.swagger-section .api-popup-dialog { + z-index: 10000; + position: absolute; + width: 500px; + background: #FFF; + padding: 20px; + border: 1px solid #ccc; + border-radius: 5px; + display: none; + font-size: 13px; + color: #777; +} +.swagger-section .api-popup-dialog .api-popup-title { + font-size: 24px; + padding: 10px 0; +} +.swagger-section .api-popup-dialog .api-popup-title { + font-size: 24px; + padding: 10px 0; +} +.swagger-section .api-popup-dialog p.error-msg { + padding-left: 5px; + padding-bottom: 5px; +} +.swagger-section .api-popup-dialog button.api-popup-authbtn { + height: 30px; +} +.swagger-section .api-popup-dialog button.api-popup-cancel { + height: 30px; +} +.swagger-section .api-popup-scopes { + padding: 10px 20px; +} +.swagger-section .api-popup-scopes li { + padding: 5px 0; + line-height: 20px; +} +.swagger-section .api-popup-scopes .api-scope-desc { + padding-left: 20px; + font-style: italic; +} +.swagger-section .api-popup-scopes li input { + position: relative; + top: 2px; +} +.swagger-section .api-popup-actions { + padding-top: 10px; +} +.swagger-section .access { + float: right; +} +.swagger-section .auth { + float: right; +} +.swagger-section #api_information_panel { + position: absolute; + background: #FFF; + border: 1px solid #ccc; + border-radius: 5px; + display: none; + font-size: 13px; + max-width: 300px; + line-height: 30px; + color: black; + padding: 5px; +} +.swagger-section #api_information_panel p .api-msg-enabled { + color: green; +} +.swagger-section #api_information_panel p .api-msg-disabled { + color: red; +} +.swagger-section .api-ic { + height: 18px; + vertical-align: middle; + display: inline-block; + background: url(../images/explorer_icons.png) no-repeat; +} +.swagger-section .ic-info { + background-position: 0 0; + width: 18px; + margin-top: -7px; + margin-left: 4px; +} +.swagger-section .ic-warning { + background-position: -60px 0; + width: 18px; + margin-top: -7px; + margin-left: 4px; +} +.swagger-section .ic-error { + background-position: -30px 0; + width: 18px; + margin-top: -7px; + margin-left: 4px; +} +.swagger-section .ic-off { + background-position: -90px 0; + width: 58px; + margin-top: -4px; + cursor: pointer; +} +.swagger-section .ic-on { + background-position: -160px 0; + width: 58px; + margin-top: -4px; + cursor: pointer; +} +.swagger-section #header { + background-color: #bbb; + box-shadow: 0 2px 8px #777; + padding: 10px; +} +.swagger-section #footer { + color: #999; + font-style: italic; + font-size: small; + padding: 14px; +} + +.swagger-section #header a#logo { + font-size: 1.6em; + font-weight: bold; + text-decoration: none; + background: transparent url(../images/onos-logo.png) no-repeat left center; + padding: 20px 0 20px 60px; + color: white; +} +.swagger-section #header form#api_selector { + display: block; + clear: none; + float: right; +} +.swagger-section #header form#api_selector .input { + display: block; + clear: none; + float: left; + margin: 0 10px 0 0; +} +.swagger-section #header form#api_selector .input input#input_apiKey { + width: 200px; + border-radius: 3px; + border: solid 2px #fff; +} +.swagger-section #header form#api_selector .input input#input_baseUrl { + width: 200px; + display: none; +} +.swagger-section #header form#api_selector .input a#explore { + display: block; + text-decoration: none; + font-weight: bold; + padding: 6px 8px; + font-size: 0.9em; + color: white; + background-color: #CE5650; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -o-border-radius: 4px; + -ms-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; +} +.swagger-section #header form#api_selector .input a#explore:hover { + background-color: #CE5650; +} + +.swagger-section #header form#api_selector #selector { + width: 580px; + height: 28px; + overflow: hidden; +} +.swagger-section #header form#api_selector .input select { + background: transparent; + border: 0; + border-radius: 0; + -webkit-appearance: none; + padding: 0 8px 0 48px; + background: url(../images/nav-menu.png) no-repeat left; + background-size: 30px 30px; + outline: 0; + font-size: 14pt; + font-weight: normal; + font-style: italic; + color: #369; + width: 580px; + height: 28px; +} + +.swagger-section #header form#api_selector .input input { + font-size: 0.9em; + padding: 3px; + margin: 0; +} +.swagger-section #content_message { + margin: 10px 15px; + font-style: italic; + color: #999999; +} +.swagger-section #message-bar { + min-height: 30px; + text-align: center; + padding-top: 10px; +} diff --git a/framework/src/onos/web/api/src/main/resources/docs/css/typography.css b/framework/src/onos/web/api/src/main/resources/docs/css/typography.css new file mode 100644 index 00000000..e69de29b diff --git a/framework/src/onos/web/api/src/main/resources/docs/images/nav-menu.png b/framework/src/onos/web/api/src/main/resources/docs/images/nav-menu.png new file mode 100644 index 00000000..07bbcf1b Binary files /dev/null and b/framework/src/onos/web/api/src/main/resources/docs/images/nav-menu.png differ diff --git a/framework/src/onos/web/api/src/main/resources/docs/images/onos-logo.png b/framework/src/onos/web/api/src/main/resources/docs/images/onos-logo.png new file mode 100644 index 00000000..8688cd6b Binary files /dev/null and b/framework/src/onos/web/api/src/main/resources/docs/images/onos-logo.png differ diff --git a/framework/src/onos/web/api/src/main/resources/docs/index.html b/framework/src/onos/web/api/src/main/resources/docs/index.html new file mode 100644 index 00000000..7ed9cb10 --- /dev/null +++ b/framework/src/onos/web/api/src/main/resources/docs/index.html @@ -0,0 +1,118 @@ + + + + + ONOS API Docs + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+
+ + + diff --git a/framework/src/onos/web/api/src/main/resources/docs/lib/backbone-min.js b/framework/src/onos/web/api/src/main/resources/docs/lib/backbone-min.js new file mode 100644 index 00000000..a3f544be --- /dev/null +++ b/framework/src/onos/web/api/src/main/resources/docs/lib/backbone-min.js @@ -0,0 +1,15 @@ +// Backbone.js 1.1.2 + +(function(t,e){if(typeof define==="function"&&define.amd){define(["underscore","jquery","exports"],function(i,r,s){t.Backbone=e(t,s,i,r)})}else if(typeof exports!=="undefined"){var i=require("underscore");e(t,exports,i)}else{t.Backbone=e(t,{},t._,t.jQuery||t.Zepto||t.ender||t.$)}})(this,function(t,e,i,r){var s=t.Backbone;var n=[];var a=n.push;var o=n.slice;var h=n.splice;e.VERSION="1.1.2";e.$=r;e.noConflict=function(){t.Backbone=s;return this};e.emulateHTTP=false;e.emulateJSON=false;var u=e.Events={on:function(t,e,i){if(!c(this,"on",t,[e,i])||!e)return this;this._events||(this._events={});var r=this._events[t]||(this._events[t]=[]);r.push({callback:e,context:i,ctx:i||this});return this},once:function(t,e,r){if(!c(this,"once",t,[e,r])||!e)return this;var s=this;var n=i.once(function(){s.off(t,n);e.apply(this,arguments)});n._callback=e;return this.on(t,n,r)},off:function(t,e,r){var s,n,a,o,h,u,l,f;if(!this._events||!c(this,"off",t,[e,r]))return this;if(!t&&!e&&!r){this._events=void 0;return this}o=t?[t]:i.keys(this._events);for(h=0,u=o.length;h").attr(t);this.setElement(r,false)}else{this.setElement(i.result(this,"el"),false)}}});e.sync=function(t,r,s){var n=T[t];i.defaults(s||(s={}),{emulateHTTP:e.emulateHTTP,emulateJSON:e.emulateJSON});var a={type:n,dataType:"json"};if(!s.url){a.url=i.result(r,"url")||M()}if(s.data==null&&r&&(t==="create"||t==="update"||t==="patch")){a.contentType="application/json";a.data=JSON.stringify(s.attrs||r.toJSON(s))}if(s.emulateJSON){a.contentType="application/x-www-form-urlencoded";a.data=a.data?{model:a.data}:{}}if(s.emulateHTTP&&(n==="PUT"||n==="DELETE"||n==="PATCH")){a.type="POST";if(s.emulateJSON)a.data._method=n;var o=s.beforeSend;s.beforeSend=function(t){t.setRequestHeader("X-HTTP-Method-Override",n);if(o)return o.apply(this,arguments)}}if(a.type!=="GET"&&!s.emulateJSON){a.processData=false}if(a.type==="PATCH"&&k){a.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")}}var h=s.xhr=e.ajax(i.extend(a,s));r.trigger("request",r,h,s);return h};var k=typeof window!=="undefined"&&!!window.ActiveXObject&&!(window.XMLHttpRequest&&(new XMLHttpRequest).dispatchEvent);var T={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};e.ajax=function(){return e.$.ajax.apply(e.$,arguments)};var $=e.Router=function(t){t||(t={});if(t.routes)this.routes=t.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var S=/\((.*?)\)/g;var H=/(\(\?)?:\w+/g;var A=/\*\w+/g;var I=/[\-{}\[\]+?.,\\\^$|#\s]/g;i.extend($.prototype,u,{initialize:function(){},route:function(t,r,s){if(!i.isRegExp(t))t=this._routeToRegExp(t);if(i.isFunction(r)){s=r;r=""}if(!s)s=this[r];var n=this;e.history.route(t,function(i){var a=n._extractParameters(t,i);n.execute(s,a);n.trigger.apply(n,["route:"+r].concat(a));n.trigger("route",r,a);e.history.trigger("route",n,r,a)});return this},execute:function(t,e){if(t)t.apply(this,e)},navigate:function(t,i){e.history.navigate(t,i);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=i.result(this,"routes");var t,e=i.keys(this.routes);while((t=e.pop())!=null){this.route(t,this.routes[t])}},_routeToRegExp:function(t){t=t.replace(I,"\\$&").replace(S,"(?:$1)?").replace(H,function(t,e){return e?t:"([^/?]+)"}).replace(A,"([^?]*?)");return new RegExp("^"+t+"(?:\\?([\\s\\S]*))?$")},_extractParameters:function(t,e){var r=t.exec(e).slice(1);return i.map(r,function(t,e){if(e===r.length-1)return t||null;return t?decodeURIComponent(t):null})}});var N=e.History=function(){this.handlers=[];i.bindAll(this,"checkUrl");if(typeof window!=="undefined"){this.location=window.location;this.history=window.history}};var R=/^[#\/]|\s+$/g;var O=/^\/+|\/+$/g;var P=/msie [\w.]+/;var C=/\/$/;var j=/#.*$/;N.started=false;i.extend(N.prototype,u,{interval:50,atRoot:function(){return this.location.pathname.replace(/[^\/]$/,"$&/")===this.root},getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getFragment:function(t,e){if(t==null){if(this._hasPushState||!this._wantsHashChange||e){t=decodeURI(this.location.pathname+this.location.search);var i=this.root.replace(C,"");if(!t.indexOf(i))t=t.slice(i.length)}else{t=this.getHash()}}return t.replace(R,"")},start:function(t){if(N.started)throw new Error("Backbone.history has already been started");N.started=true;this.options=i.extend({root:"/"},this.options,t);this.root=this.options.root;this._wantsHashChange=this.options.hashChange!==false;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var r=this.getFragment();var s=document.documentMode;var n=P.exec(navigator.userAgent.toLowerCase())&&(!s||s<=7);this.root=("/"+this.root+"/").replace(O,"/");if(n&&this._wantsHashChange){var a=e.$('