You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by ma...@apache.org on 2018/08/03 13:07:45 UTC

[incubator-openwhisk] branch master updated: Validate the Controller Swagger spec matches the impl (#3878)

This is an automated email from the ASF dual-hosted git repository.

markusthoemmes pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk.git


The following commit(s) were added to refs/heads/master by this push:
     new 68f472d  Validate the Controller Swagger spec matches the impl (#3878)
68f472d is described below

commit 68f472dbf62e322c39520b23aa6d6cf357efa226
Author: Ben Browning <be...@gmail.com>
AuthorDate: Fri Aug 3 09:07:41 2018 -0400

    Validate the Controller Swagger spec matches the impl (#3878)
    
    This change wires up all existing API tests that use WskRestOperations
    to validate the HTTP requests and responses against the Controller
    API's Swagger spec.
    
    It also adds a new Gradle task - :tests:testSwaggerCodegen - that does
    a basic validation of the Swagger spec and generates a basic Java
    client from that spec and verifies that the client compiles. CI is
    wired up to run this new test task.
    
    The result of both of the above are a number of changes to the Swagger
    spec, also included here. This brings our Swagger spec much closer to
    matching the reality of the Controller API, and any additional
    coverage in the API test suite will further increase coverage of the
    Swagger spec.
    
    There are some existing tests that purposely test API responses when
    passed invalid data, so the new SwaggerValidator trait includes a
    small whilelist of invalid requests and responses to accept so the
    build does not fail in these scenarios.
    
    Based on review feedback, this eagerly converts all HTTP entities to
    their Strict variants which allows both the SwaggerValidator and
    downstream consumers to read the request/response bodies without
    making a copy.
    
    This also exposed another slight change needed in apiv1swagger.json
    where rule enabling/disabling can sometimes return a text/plain body
    instead of json.
---
 .../src/main/resources/apiv1swagger.json           | 810 +++++++++++++++++----
 tests/build.gradle                                 |  24 +
 tests/src/test/resources/swagger-config.json       |   4 +
 .../test/scala/common/rest/SwaggerValidator.scala  | 115 +++
 .../test/scala/common/rest/WskRestOperations.scala |  19 +-
 tools/travis/runTests.sh                           |   2 +-
 6 files changed, 827 insertions(+), 147 deletions(-)

diff --git a/core/controller/src/main/resources/apiv1swagger.json b/core/controller/src/main/resources/apiv1swagger.json
index 80eefbb..989c5fa 100644
--- a/core/controller/src/main/resources/apiv1swagger.json
+++ b/core/controller/src/main/resources/apiv1swagger.json
@@ -9,6 +9,16 @@
         "application/json"
     ],
     "basePath": "/api/v1",
+    "securityDefinitions": {
+        "basicAuth": {
+            "type": "basic"
+        }
+    },
+    "security": [
+        {
+            "basicAuth": []
+        }
+    ],
     "tags": [
         {
             "name": "Actions"
@@ -100,7 +110,7 @@
                         "schema": {
                             "type": "array",
                             "items": {
-                                "$ref": "#/definitions/EntityBrief"
+                                "$ref": "#/definitions/Action"
                             }
                         }
                     },
@@ -114,6 +124,22 @@
             }
         },
         "/namespaces/{namespace}/actions/{actionName}": {
+            "parameters": [
+                {
+                    "name": "namespace",
+                    "in": "path",
+                    "description": "The entity namespace",
+                    "required": true,
+                    "type": "string"
+                },
+                {
+                    "name": "actionName",
+                    "in": "path",
+                    "description": "Name of action to fetch",
+                    "required": true,
+                    "type": "string"
+                }
+            ],
             "get": {
                 "tags": [
                     "Actions"
@@ -123,20 +149,6 @@
                 "operationId": "getActionByName",
                 "parameters": [
                     {
-                        "name": "namespace",
-                        "in": "path",
-                        "description": "The entity namespace",
-                        "required": true,
-                        "type": "string"
-                    },
-                    {
-                        "name": "actionName",
-                        "in": "path",
-                        "description": "Name of action to fetch",
-                        "required": true,
-                        "type": "string"
-                    },
-                    {
                         "name": "code",
                         "in": "query",
                         "description": "Include action code in the result",
@@ -157,6 +169,9 @@
                     "401": {
                         "$ref": "#/responses/UnauthorizedRequest"
                     },
+                    "403": {
+                        "$ref": "#/responses/UnauthorizedRequest"
+                    },
                     "404": {
                         "$ref": "#/responses/ItemNotFound"
                     },
@@ -174,20 +189,6 @@
                 "operationId": "updateAction",
                 "parameters": [
                     {
-                        "name": "namespace",
-                        "in": "path",
-                        "description": "The entity namespace",
-                        "required": true,
-                        "type": "string"
-                    },
-                    {
-                        "name": "actionName",
-                        "in": "path",
-                        "description": "Name of action",
-                        "required": true,
-                        "type": "string"
-                    },
-                    {
                         "name": "overwrite",
                         "in": "query",
                         "description": "Overwrite item if it exists. Default is false.",
@@ -216,7 +217,10 @@
                 ],
                 "responses": {
                     "200": {
-                        "$ref": "#/responses/UpdatedItem"
+                        "description": "Updated Action",
+                        "schema": {
+                            "$ref": "#/definitions/Action"
+                        }
                     },
                     "400": {
                         "$ref": "#/responses/BadRequest"
@@ -224,6 +228,9 @@
                     "401": {
                         "$ref": "#/responses/UnauthorizedRequest"
                     },
+                    "403": {
+                        "$ref": "#/responses/UnauthorizedRequest"
+                    },
                     "409": {
                         "$ref": "#/responses/Conflict"
                     },
@@ -242,29 +249,167 @@
                 "description": "Delete an action",
                 "summary": "Delete an action",
                 "operationId": "deleteAction",
+                "responses": {
+                    "200": {
+                        "$ref": "#/responses/DeletedItem"
+                    },
+                    "400": {
+                        "$ref": "#/responses/BadRequest"
+                    },
+                    "401": {
+                        "$ref": "#/responses/UnauthorizedRequest"
+                    },
+                    "403": {
+                        "$ref": "#/responses/UnauthorizedRequest"
+                    },
+                    "404": {
+                        "$ref": "#/responses/ItemNotFound"
+                    },
+                    "409": {
+                        "$ref": "#/responses/Conflict"
+                    },
+                    "500": {
+                        "$ref": "#/responses/ServerError"
+                    }
+                }
+            },
+            "post": {
+                "tags": [
+                    "Actions"
+                ],
+                "description": "Invoke an action",
+                "summary": "Invoke an action",
+                "operationId": "invokeAction",
                 "parameters": [
                     {
-                        "name": "namespace",
-                        "in": "path",
-                        "description": "The entity namespace",
-                        "required": true,
-                        "type": "string"
+                        "name": "blocking",
+                        "in": "query",
+                        "description": "Blocking or non-blocking invocation. Default is non-blocking.",
+                        "required": false,
+                        "type": "string",
+                        "enum": [
+                            "true",
+                            "false"
+                        ]
                     },
                     {
-                        "name": "actionName",
-                        "in": "path",
-                        "description": "Name of action",
-                        "required": true,
-                        "type": "string"
+                        "name": "result",
+                        "in": "query",
+                        "description": "Return only the result of a blocking activation. Default is false.",
+                        "required": false,
+                        "type": "string",
+                        "enum": [
+                            "true",
+                            "false"
+                        ]
+                    },
+                    {
+                        "name": "timeout",
+                        "in": "query",
+                        "description": "Wait no more than specified duration in milliseconds for a blocking response. Default value and max allowed timeout are 60000.",
+                        "required": false,
+                        "type": "integer"
+                    },
+                    {
+                        "name": "payload",
+                        "in": "body",
+                        "description": "The parameters for the action being invoked",
+                        "required": false,
+                        "schema": {
+                            "type": "object"
+                        }
                     }
                 ],
+                "consumes": [
+                    "application/json"
+                ],
                 "responses": {
                     "200": {
-                        "$ref": "#/responses/DeletedItem"
+                        "description": "Successful activation"
+                    },
+                    "202": {
+                        "$ref": "#/responses/AcceptedActivation"
+                    },
+                    "401": {
+                        "$ref": "#/responses/UnauthorizedRequest"
+                    },
+                    "403": {
+                        "$ref": "#/responses/UnauthorizedRequest"
+                    },
+                    "404": {
+                        "$ref": "#/responses/ItemNotFound"
+                    },
+                    "408": {
+                        "$ref": "#/responses/Timeout"
+                    },
+                    "429": {
+                        "$ref": "#/responses/TooManyRequsts"
+                    },
+                    "500": {
+                        "$ref": "#/responses/ServerError"
+                    },
+                    "502": {
+                        "description": "Activation produced an application error"
+                    }
+                }
+            }
+        },
+        "/namespaces/{namespace}/actions/{packageName}/{actionName}": {
+            "parameters": [
+                {
+                    "name": "namespace",
+                    "in": "path",
+                    "description": "The entity namespace",
+                    "required": true,
+                    "type": "string"
+                },
+                {
+                    "name": "packageName",
+                    "in": "path",
+                    "description": "Name of package that contains action",
+                    "required": true,
+                    "type": "string"
+                },
+                {
+                    "name": "actionName",
+                    "in": "path",
+                    "description": "Name of action to fetch",
+                    "required": true,
+                    "type": "string"
+                }
+            ],
+            "get": {
+                "tags": [
+                    "Actions"
+                ],
+                "summary": "Get action information",
+                "description": "Get action information.",
+                "operationId": "getActionInPackageByName",
+                "parameters": [
+                    {
+                        "name": "code",
+                        "in": "query",
+                        "description": "Include action code in the result",
+                        "required": false,
+                        "type": "boolean"
+                    }
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "responses": {
+                    "200": {
+                        "description": "Returned action",
+                        "schema": {
+                            "$ref": "#/definitions/Action"
+                        }
                     },
                     "401": {
                         "$ref": "#/responses/UnauthorizedRequest"
                     },
+                    "403": {
+                        "$ref": "#/responses/UnauthorizedRequest"
+                    },
                     "404": {
                         "$ref": "#/responses/ItemNotFound"
                     },
@@ -273,28 +418,107 @@
                     }
                 }
             },
-            "post": {
+            "put": {
                 "tags": [
                     "Actions"
                 ],
-                "description": "Invoke an action",
-                "summary": "Invoke an action",
-                "operationId": "invokeAction",
+                "description": "Create or update an action",
+                "summary": "Create or update an action",
+                "operationId": "updateActionInPackage",
                 "parameters": [
                     {
-                        "name": "namespace",
-                        "in": "path",
-                        "description": "The entity namespace",
-                        "required": true,
-                        "type": "string"
+                        "name": "overwrite",
+                        "in": "query",
+                        "description": "Overwrite item if it exists. Default is false.",
+                        "required": false,
+                        "type": "string",
+                        "enum": [
+                            "true",
+                            "false"
+                        ]
                     },
                     {
-                        "name": "actionName",
-                        "in": "path",
-                        "description": "Name of action",
+                        "name": "action",
+                        "in": "body",
+                        "description": "The action being updated",
                         "required": true,
-                        "type": "string"
+                        "schema": {
+                            "$ref": "#/definitions/ActionPut"
+                        }
+                    }
+                ],
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "responses": {
+                    "200": {
+                        "description": "Updated Action",
+                        "schema": {
+                            "$ref": "#/definitions/Action"
+                        }
+                    },
+                    "400": {
+                        "$ref": "#/responses/BadRequest"
+                    },
+                    "401": {
+                        "$ref": "#/responses/UnauthorizedRequest"
+                    },
+                    "403": {
+                        "$ref": "#/responses/UnauthorizedRequest"
+                    },
+                    "409": {
+                        "$ref": "#/responses/Conflict"
+                    },
+                    "413": {
+                        "$ref": "#/responses/RequestEntityTooLarge"
                     },
+                    "500": {
+                        "$ref": "#/responses/ServerError"
+                    }
+                }
+            },
+            "delete": {
+                "tags": [
+                    "Actions"
+                ],
+                "description": "Delete an action",
+                "summary": "Delete an action",
+                "operationId": "deleteActionInPackage",
+                "responses": {
+                    "200": {
+                        "$ref": "#/responses/DeletedItem"
+                    },
+                    "400": {
+                        "$ref": "#/responses/BadRequest"
+                    },
+                    "401": {
+                        "$ref": "#/responses/UnauthorizedRequest"
+                    },
+                    "403": {
+                        "$ref": "#/responses/UnauthorizedRequest"
+                    },
+                    "404": {
+                        "$ref": "#/responses/ItemNotFound"
+                    },
+                    "409": {
+                        "$ref": "#/responses/Conflict"
+                    },
+                    "500": {
+                        "$ref": "#/responses/ServerError"
+                    }
+                }
+            },
+            "post": {
+                "tags": [
+                    "Actions"
+                ],
+                "description": "Invoke an action",
+                "summary": "Invoke an action",
+                "operationId": "invokeActionInPackage",
+                "parameters": [
                     {
                         "name": "blocking",
                         "in": "query",
@@ -328,39 +552,125 @@
                         "name": "payload",
                         "in": "body",
                         "description": "The parameters for the action being invoked",
-                        "required": true,
+                        "required": false,
+                        "schema": {
+                            "type": "object"
+                        }
+                    }
+                ],
+                "consumes": [
+                    "application/json"
+                ],
+                "responses": {
+                    "200": {
+                        "description": "Successful activation"
+                    },
+                    "202": {
+                        "$ref": "#/responses/AcceptedActivation"
+                    },
+                    "401": {
+                        "$ref": "#/responses/UnauthorizedRequest"
+                    },
+                    "403": {
+                        "$ref": "#/responses/UnauthorizedRequest"
+                    },
+                    "404": {
+                        "$ref": "#/responses/ItemNotFound"
+                    },
+                    "408": {
+                        "$ref": "#/responses/Timeout"
+                    },
+                    "429": {
+                        "$ref": "#/responses/TooManyRequsts"
+                    },
+                    "500": {
+                        "$ref": "#/responses/ServerError"
+                    },
+                    "502": {
+                        "description": "Activation produced an application error"
+                    }
+                }
+            }
+        },
+        "/web/{namespace}/{packageName}/{actionName}.{extension}": {
+            "parameters": [
+                {
+                    "name": "namespace",
+                    "type": "string",
+                    "in": "path",
+                    "required": true
+                },
+                {
+                    "name": "packageName",
+                    "type": "string",
+                    "in": "path",
+                    "required": true
+                },
+                {
+                    "name": "actionName",
+                    "type": "string",
+                    "in": "path",
+                    "required": true
+                },
+                {
+                    "name": "extension",
+                    "type": "string",
+                    "in": "path",
+                    "required": true
+                }
+            ],
+            "get": {
+                "tags": [
+                    "Actions"
+                ],
+                "responses": {
+                    "default": {
+                        "description": "any response",
+                        "schema": {}
+                    }
+                }
+            },
+            "put": {
+                "tags": [
+                    "Actions"
+                ],
+                "responses": {
+                    "default": {
+                        "description": "any response",
+                        "schema": {}
+                    }
+                }
+            },
+            "delete": {
+                "tags": [
+                    "Actions"
+                ],
+                "responses": {
+                    "default": {
+                        "description": "any response",
+                        "schema": {}
+                    }
+                }
+            },
+            "post": {
+                "tags": [
+                    "Actions"
+                ],
+                "parameters": [
+                    {
+                        "name": "payload",
+                        "in": "body",
+                        "description": "The parameters for the action being invoked",
+                        "required": false,
                         "schema": {
-                            "$ref": "#/definitions/KeyValue"
+                            "type": "object"
                         }
                     }
                 ],
                 "responses": {
-                    "200": {
-                        "description": "Successful activation",
-                        "schema": {
-                            "$ref": "#/definitions/Activation"
-                        }
-                    },
-                    "202": {
-                        "$ref": "#/responses/AcceptedActivation"
-                    },
-                    "401": {
-                        "$ref": "#/responses/UnauthorizedRequest"
-                    },
-                    "404": {
-                        "$ref": "#/responses/ItemNotFound"
-                    },
-                    "408": {
-                        "$ref": "#/responses/Timeout"
-                    },
-                    "500": {
-                        "$ref": "#/responses/ServerError"
-                    },
-                    "502": {
-                        "description": "Activation produced an application error",
-                        "schema": {
-                            "$ref": "#/definitions/Activation"
-                        }
+                    "default": {
+                        "description": "any response",
+                        "schema": {}
                     }
                 }
             }
@@ -405,7 +715,7 @@
                         "schema": {
                             "type": "array",
                             "items": {
-                                "$ref": "#/definitions/EntityBrief"
+                                "$ref": "#/definitions/Rule"
                             }
                         }
                     },
@@ -514,7 +824,10 @@
                 ],
                 "responses": {
                     "200": {
-                        "$ref": "#/responses/UpdatedItem"
+                        "description": "Updated rule",
+                        "schema": {
+                            "$ref": "#/definitions/Rule"
+                        }
                     },
                     "400": {
                         "$ref": "#/responses/BadRequest"
@@ -522,6 +835,9 @@
                     "401": {
                         "$ref": "#/responses/UnauthorizedRequest"
                     },
+                    "404": {
+                        "$ref": "#/responses/ItemNotFound"
+                    },
                     "409": {
                         "$ref": "#/responses/Conflict"
                     },
@@ -594,23 +910,34 @@
                         "type": "string"
                     },
                     {
-                        "name": "state",
-                        "in": "query",
-                        "type": "string",
-                        "description": "Set state to enable or disable",
+                        "name": "status",
+                        "in": "body",
+                        "description": "Set status to active or inactive",
                         "required": true,
-                        "enum": [
-                            "disabled",
-                            "enabled"
-                        ]
+                        "schema": {
+                            "type": "object",
+                            "required": [
+                                "status"
+                            ],
+                            "properties": {
+                                "status": {
+                                    "type": "string",
+                                    "enum": [
+                                        "inactive",
+                                        "active"
+                                    ]
+                                }
+                            }
+                        }
                     }
                 ],
                 "produces": [
-                    "application/json"
+                    "application/json",
+                    "text/plain"
                 ],
                 "responses": {
                     "200": {
-                        "$ref": "#/responses/UpdatedItem"
+                        "$ref": "#/responses/AcceptedRuleStateChange"
                     },
                     "202": {
                         "$ref": "#/responses/AcceptedRuleStateChange"
@@ -621,6 +948,9 @@
                     "401": {
                         "$ref": "#/responses/UnauthorizedRequest"
                     },
+                    "404": {
+                        "$ref": "#/responses/ItemNotFound"
+                    },
                     "500": {
                         "$ref": "#/responses/ServerError"
                     }
@@ -667,7 +997,7 @@
                         "schema": {
                             "type": "array",
                             "items": {
-                                "$ref": "#/definitions/EntityBrief"
+                                "$ref": "#/definitions/Trigger"
                             }
                         }
                     },
@@ -776,7 +1106,10 @@
                 ],
                 "responses": {
                     "200": {
-                        "$ref": "#/responses/UpdatedItem"
+                        "description": "Updated trigger",
+                        "schema": {
+                            "$ref": "#/definitions/Trigger"
+                        }
                     },
                     "400": {
                         "$ref": "#/responses/BadRequest"
@@ -859,9 +1192,9 @@
                         "name": "payload",
                         "in": "body",
                         "description": "The trigger payload",
-                        "required": true,
+                        "required": false,
                         "schema": {
-                            "$ref": "#/definitions/KeyValue"
+                            "type": "object"
                         }
                     }
                 ],
@@ -869,7 +1202,7 @@
                     "202": {
                         "description": "Activation id",
                         "schema": {
-                            "$ref": "#/definitions/ItemId"
+                            "$ref": "#/definitions/ActivationId"
                         }
                     },
                     "204": {
@@ -884,6 +1217,9 @@
                     "408": {
                         "$ref": "#/responses/Timeout"
                     },
+                    "429": {
+                        "$ref": "#/responses/TooManyRequests"
+                    },
                     "500": {
                         "$ref": "#/responses/ServerError"
                     }
@@ -897,7 +1233,7 @@
                 ],
                 "description": "Get all packages",
                 "summary": "Get all packages",
-                "operationId": "getAlPackages",
+                "operationId": "getAllPackages",
                 "parameters": [
                     {
                         "name": "namespace",
@@ -937,7 +1273,7 @@
                         "schema": {
                             "type": "array",
                             "items": {
-                                "$ref": "#/definitions/EntityBrief"
+                                "$ref": "#/definitions/Package"
                             }
                         }
                     },
@@ -990,6 +1326,9 @@
                     "404": {
                         "$ref": "#/responses/ItemNotFound"
                     },
+                    "409": {
+                        "$ref": "#/responses/Conflict"
+                    },
                     "500": {
                         "$ref": "#/responses/ServerError"
                     }
@@ -1046,7 +1385,10 @@
                 ],
                 "responses": {
                     "200": {
-                        "$ref": "#/responses/UpdatedItem"
+                        "description": "Updated Package",
+                        "schema": {
+                            "$ref": "#/definitions/Package"
+                        }
                     },
                     "400": {
                         "$ref": "#/responses/BadRequest"
@@ -1054,6 +1396,9 @@
                     "401": {
                         "$ref": "#/responses/UnauthorizedRequest"
                     },
+                    "403": {
+                        "$ref": "#/responses/UnauthorizedRequest"
+                    },
                     "409": {
                         "$ref": "#/responses/Conflict"
                     },
@@ -1098,6 +1443,9 @@
                     "404": {
                         "$ref": "#/responses/ItemNotFound"
                     },
+                    "409": {
+                        "$ref": "#/responses/Conflict"
+                    },
                     "500": {
                         "$ref": "#/responses/ServerError"
                     }
@@ -1324,10 +1672,6 @@
     },
     "definitions": {
         "KeyValue": {
-            "required": [
-                "key",
-                "value"
-            ],
             "properties": {
                 "key": {
                     "type": "string"
@@ -1347,6 +1691,20 @@
                 }
             }
         },
+        "PathName": {
+            "required": [
+                "path",
+                "name"
+            ],
+            "properties": {
+                "path": {
+                    "type": "string"
+                },
+                "name": {
+                    "type": "string"
+                }
+            }
+        },
         "ErrorMessage": {
             "required": [
                 "error"
@@ -1354,6 +1712,9 @@
             "properties": {
                 "error": {
                     "type": "string"
+                },
+                "code": {
+                    "type": "string"
                 }
             }
         },
@@ -1363,12 +1724,20 @@
                 "timeout": {
                     "type": "integer",
                     "format": "int32",
-                    "default": 30000
+                    "description": "timeout in milliseconds",
+                    "default": 60000
                 },
                 "memory": {
                     "type": "integer",
                     "format": "int32",
+                    "description": "memory in megabytes",
                     "default": 256
+                },
+                "logs": {
+                    "type": "integer",
+                    "format": "int32",
+                    "description": "log size in megabytes",
+                    "default": 10
                 }
             }
         },
@@ -1408,7 +1777,6 @@
                 "version",
                 "publish",
                 "exec",
-                "parameters",
                 "limits"
             ],
             "properties": {
@@ -1450,12 +1818,26 @@
                 },
                 "limits": {
                     "$ref": "#/definitions/ActionLimits"
+                },
+                "updated": {
+                    "type": "integer",
+                    "description": "Time when the action was updated"
                 }
             }
         },
         "ActionPut": {
-            "description": "A restricted Action view that elides properties that are auto-assigned or derived from the URI (i.e., the namespace and name).",
+            "description": "A restricted Action view used when updating an Action",
             "properties": {
+                "namespace": {
+                    "type": "string",
+                    "description": "Namespace of the item",
+                    "minLength": 1
+                },
+                "name": {
+                    "type": "string",
+                    "description": "Name of the item",
+                    "minLength": 1
+                },
                 "version": {
                     "type": "string",
                     "description": "Semantic version of the item",
@@ -1488,22 +1870,23 @@
             }
         },
         "ActionExec": {
-            "required": [
-                "kind"
-            ],
             "properties": {
                 "kind": {
                     "type": "string",
                     "enum": [
                         "blackbox",
                         "java",
+                        "java:default",
                         "nodejs:6",
                         "nodejs:8",
+                        "nodejs:default",
                         "php:7.1",
                         "php:7.2",
                         "python:2",
                         "python:3",
+                        "python:default",
                         "ruby:2.5",
+                        "sequence",
                         "swift:3.1.1",
                         "swift:4.1"
                     ],
@@ -1517,9 +1900,24 @@
                     "type": "string",
                     "description": "container image name when kind is 'blackbox'"
                 },
+                "main": {
+                    "type": "string",
+                    "description": "main entrypoint of the action code"
+                },
                 "init": {
                     "type": "string",
                     "description": "optional zipfile reference when code kind is 'nodejs'"
+                },
+                "binary": {
+                    "type": "boolean",
+                    "description": "Whether the action has a binary attachment or not"
+                },
+                "components": {
+                    "type": "array",
+                    "description": "For sequence actions, the individual action components",
+                    "items": {
+                        "type": "string"
+                    }
                 }
             },
             "description": "definition of the action, such as javascript code or the name of a container"
@@ -1541,7 +1939,6 @@
                 "name",
                 "version",
                 "publish",
-                "status",
                 "trigger",
                 "action"
             ],
@@ -1565,6 +1962,13 @@
                     "type": "boolean",
                     "description": "Whether to publish the item or not"
                 },
+                "annotations": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/KeyValue"
+                    },
+                    "description": "annotations on the item"
+                },
                 "status": {
                     "type": "string",
                     "description": "Status of a rule",
@@ -1576,20 +1980,21 @@
                     ]
                 },
                 "trigger": {
-                    "type": "string",
-                    "description": "Name of the trigger",
-                    "minLength": 1
+                    "$ref": "#/definitions/PathName"
                 },
                 "action": {
-                    "type": "string",
-                    "description": "Name of the action",
-                    "minLength": 1
+                    "$ref": "#/definitions/PathName"
                 }
             }
         },
         "RulePut": {
-            "description": "A restricted Rule view that elides properties that are auto-assigned or derived from the URI (i.e., the namespace and name).",
+            "description": "A restricted Rule view used when updating a Rule",
             "properties": {
+                "name": {
+                    "type": "string",
+                    "description": "Name of the item",
+                    "minLength": 1
+                },
                 "version": {
                     "type": "string",
                     "description": "Semantic version of the item",
@@ -1599,6 +2004,22 @@
                     "type": "boolean",
                     "description": "Whether to publish the item or not"
                 },
+                "annotations": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/KeyValue"
+                    },
+                    "description": "annotations on the item"
+                },
+                "status": {
+                    "type": "string",
+                    "description": "Status of a rule",
+                    "enum": [
+                        "active",
+                        "inactive",
+                        ""
+                    ]
+                },
                 "trigger": {
                     "type": "string",
                     "description": "Name of the trigger",
@@ -1616,9 +2037,7 @@
                 "namespace",
                 "name",
                 "version",
-                "publish",
-                "parameters",
-                "limits"
+                "publish"
             ],
             "properties": {
                 "namespace": {
@@ -1660,12 +2079,26 @@
                 "rules": {
                     "type": "object",
                     "description": "rules associated with the trigger"
+                },
+                "updated": {
+                    "type": "integer",
+                    "description": "Time when the trigger was updated"
                 }
             }
         },
         "TriggerPut": {
-            "description": "A restricted Trigger view that elides properties that are auto-assigned or derived from the URI (i.e., the namespace and name).",
+            "description": "A restricted Trigger view used when updating the Trigger",
             "properties": {
+                "namespace": {
+                    "type": "string",
+                    "description": "Namespace of the item",
+                    "minLength": 1
+                },
+                "name": {
+                    "type": "string",
+                    "description": "Name of the item",
+                    "minLength": 1
+                },
                 "version": {
                     "type": "string",
                     "description": "Semantic version of the item",
@@ -1714,8 +2147,7 @@
                 "namespace",
                 "name",
                 "version",
-                "publish",
-                "parameters"
+                "publish"
             ],
             "properties": {
                 "namespace": {
@@ -1753,12 +2185,40 @@
                 },
                 "binding": {
                     "$ref": "#/definitions/PackageBinding"
+                },
+                "actions": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/PackageAction"
+                    },
+                    "description": "Actions contained in this package"
+                },
+                "feeds": {
+                    "type": "array",
+                    "items": {
+                        "type": "object"
+                    },
+                    "description": "Feeds contained in this package"
+                },
+                "updated": {
+                    "type": "integer",
+                    "description": "Time when the package was updated"
                 }
             }
         },
         "PackagePut": {
-            "description": "A restricted Package view that elides properties that are auto-assigned or derived from the URI (i.e., the namespace and name).",
+            "description": "A restricted Package view used when updating a Package",
             "properties": {
+                "namespace": {
+                    "type": "string",
+                    "description": "Namespace of the item",
+                    "minLength": 1
+                },
+                "name": {
+                    "type": "string",
+                    "description": "Name of the item",
+                    "minLength": 1
+                },
                 "version": {
                     "type": "string",
                     "description": "Semantic version of the item",
@@ -1788,10 +2248,6 @@
             }
         },
         "PackageBinding": {
-            "required": [
-                "namespace",
-                "name"
-            ],
             "properties": {
                 "namespace": {
                     "type": "string",
@@ -1803,6 +2259,39 @@
                 }
             }
         },
+        "PackageAction": {
+            "description": "A restricted Action view used when listing actions in a package",
+            "required": [
+                "name",
+                "version"
+            ],
+            "properties": {
+                "name": {
+                    "type": "string",
+                    "description": "Name of the item",
+                    "minLength": 1
+                },
+                "version": {
+                    "type": "string",
+                    "description": "Semantic version of the item",
+                    "minLength": 1
+                },
+                "annotations": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/KeyValue"
+                    },
+                    "description": "annotations on the item"
+                },
+                "parameters": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/KeyValue"
+                    },
+                    "description": "parameter bindings included in the context passed to the action"
+                }
+            }
+        },
         "Activation": {
             "required": [
                 "namespace",
@@ -1812,8 +2301,7 @@
                 "subject",
                 "activationId",
                 "start",
-                "end",
-                "result",
+                "response",
                 "logs"
             ],
             "properties": {
@@ -1833,6 +2321,13 @@
                     "type": "boolean",
                     "description": "Whether to publish the item or not"
                 },
+                "annotations": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/KeyValue"
+                    },
+                    "description": "annotations on the item"
+                },
                 "subject": {
                     "type": "string",
                     "description": "The subject that activated the item"
@@ -1842,19 +2337,40 @@
                     "description": "Id of the activation"
                 },
                 "start": {
-                    "type": "string",
+                    "type": "integer",
                     "description": "Time when the activation began"
                 },
                 "end": {
-                    "type": "string",
+                    "type": "integer",
                     "description": "Time when the activation completed"
                 },
-                "result": {
+                "duration": {
+                    "type": "integer",
+                    "description": "How long the invocation took, in millisecnods"
+                },
+                "response": {
                     "$ref": "#/definitions/ActivationResult"
                 },
                 "logs": {
+                    "type": "array",
+                    "description": "Logs generated by the activation",
+                    "items": {
+                        "type": "string"
+                    }
+                },
+                "cause": {
                     "type": "string",
-                    "description": "Logs generated by the activation"
+                    "description": "the activation id that caused this activation"
+                }
+            }
+        },
+        "ActivationId": {
+            "required": [
+                "activationId"
+            ],
+            "properties": {
+                "activationId": {
+                    "type": "string"
                 }
             }
         },
@@ -1864,7 +2380,7 @@
                     "type": "array",
                     "description": "Array of activation ids",
                     "items": {
-                        "type": "string"
+                        "$ref": "#/definitions/ActivationId"
                     }
                 }
             }
@@ -1901,8 +2417,11 @@
         "ActivationLogs": {
             "properties": {
                 "logs": {
-                    "type": "string",
-                    "description": "Interleaved standard output and error of an activation"
+                    "type": "array",
+                    "description": "Interleaved standard output and error of an activation",
+                    "items": {
+                        "type": "string"
+                    }
                 }
             }
         },
@@ -1920,8 +2439,12 @@
                     "type": "string",
                     "description": "Exit status of the activation"
                 },
-                "value": {
+                "result": {
                     "description": "The return value from the activation"
+                },
+                "success": {
+                    "type": "boolean",
+                    "description": "Whether the activation was successful or not"
                 }
             }
         },
@@ -2037,7 +2560,7 @@
         "AcceptedActivation": {
             "description": "Accepted activation request",
             "schema": {
-                "$ref": "#/definitions/ItemId"
+                "$ref": "#/definitions/ActivationId"
             }
         },
         "AcceptedRuleStateChange": {
@@ -2051,6 +2574,9 @@
         },
         "NoActiveRules": {
             "description": "Trigger has no active rules"
+        },
+        "TooManyRequests": {
+            "description": "Too many requests in a given time period"
         }
     }
 }
diff --git a/tests/build.gradle b/tests/build.gradle
index eae7249..15dd333 100644
--- a/tests/build.gradle
+++ b/tests/build.gradle
@@ -18,6 +18,10 @@
 import org.scoverage.ScoverageReport
 import static groovy.json.JsonOutput.*
 
+plugins {
+    id 'org.hidetake.swagger.generator' version '2.12.0'
+}
+
 apply plugin: 'scala'
 apply plugin: 'eclipse'
 apply plugin: 'maven'
@@ -147,6 +151,7 @@ dependencies {
     compile "org.mockito:mockito-core:2.15.0"
     compile 'io.opentracing:opentracing-mock:0.31.0'
     compile "org.apache.curator:curator-test:${gradle.curator.version}"
+    compile 'com.atlassian.oai:swagger-request-validator-core:1.4.5'
 
     compile "com.amazonaws:aws-java-sdk-s3:1.11.295"
 
@@ -158,6 +163,8 @@ dependencies {
 
 
     scoverage gradle.scoverage.deps
+
+    swaggerCodegen 'io.swagger:swagger-codegen-cli:2.3.1'
 }
 
 tasks.withType(ScalaCompile) {
@@ -295,3 +302,20 @@ def getScoverageClasspath(Project project) {
 
     combinedClasspath + sourceSets.test.runtimeClasspath
 }
+
+swaggerSources {
+    java {
+        inputFile = file("$projectDir/../core/controller/src/main/resources/apiv1swagger.json")
+        code {
+            language = 'java'
+            configFile = file('src/test/resources/swagger-config.json')
+            dependsOn validation
+        }
+    }
+}
+
+task testSwaggerCodegen(type: GradleBuild) {
+    dependsOn swaggerSources.java.code
+    buildFile = "${buildDir}/swagger-code-java/build.gradle"
+    tasks = ['build']
+}
diff --git a/tests/src/test/resources/swagger-config.json b/tests/src/test/resources/swagger-config.json
new file mode 100644
index 0000000..f8696d3
--- /dev/null
+++ b/tests/src/test/resources/swagger-config.json
@@ -0,0 +1,4 @@
+{
+    "library": "okhttp-gson",
+    "dateLibrary": "java8"
+}
diff --git a/tests/src/test/scala/common/rest/SwaggerValidator.scala b/tests/src/test/scala/common/rest/SwaggerValidator.scala
new file mode 100644
index 0000000..b13c94e
--- /dev/null
+++ b/tests/src/test/scala/common/rest/SwaggerValidator.scala
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 common.rest
+
+import scala.collection.JavaConverters._
+
+import akka.http.scaladsl.model.HttpEntity
+import akka.http.scaladsl.model.HttpRequest
+import akka.http.scaladsl.model.HttpResponse
+import com.atlassian.oai.validator.SwaggerRequestResponseValidator
+import com.atlassian.oai.validator.model.SimpleRequest
+import com.atlassian.oai.validator.model.SimpleResponse
+import com.atlassian.oai.validator.report.ValidationReport
+import com.atlassian.oai.validator.whitelist.ValidationErrorsWhitelist
+import com.atlassian.oai.validator.whitelist.rule.WhitelistRules
+
+trait SwaggerValidator {
+  private val specWhitelist = ValidationErrorsWhitelist
+    .create()
+    .withRule(
+      "Ignore action and trigger payloads",
+      WhitelistRules.allOf(
+        WhitelistRules.messageContains("Object instance has properties which are not allowed by the schema"),
+        WhitelistRules.anyOf(
+          WhitelistRules.pathContains("/web/"),
+          WhitelistRules.pathContains("/actions/"),
+          WhitelistRules.pathContains("/triggers/")),
+        WhitelistRules.methodIs(io.swagger.models.HttpMethod.POST)))
+    .withRule(
+      "Ignore invalid action kinds",
+      WhitelistRules.allOf(
+        WhitelistRules.messageContains("kind"),
+        WhitelistRules.messageContains("Instance value"),
+        WhitelistRules.messageContains("not found"),
+        WhitelistRules.pathContains("/actions/"),
+        WhitelistRules.methodIs(io.swagger.models.HttpMethod.PUT)))
+    .withRule(
+      "Ignore tests that check for invalid DELETEs and PUTs on actions",
+      WhitelistRules.anyOf(
+        WhitelistRules.messageContains("DELETE operation not allowed on path '/api/v1/namespaces/_/actions/'"),
+        WhitelistRules.messageContains("PUT operation not allowed on path '/api/v1/namespaces/_/actions/'")))
+
+  private val specValidator = SwaggerRequestResponseValidator
+    .createFor("apiv1swagger.json")
+    .withWhitelist(specWhitelist)
+    .build()
+
+  /**
+   * Validate a HTTP request and response against the Swagger spec. Request
+   * and response bodies are passed separately so that this validation
+   * does not have to consume the body content directly from the request
+   * and response, which would prevent callers from later consuming it.
+   *
+   * @param request the HttpRequest
+   * @param response the HttpResponse
+   * @return The list of validation error messages, if any
+   */
+  def validateRequestAndResponse(request: HttpRequest, response: HttpResponse): Seq[String] = {
+    val specRequest = {
+      val builder = new SimpleRequest.Builder(request.method.value, request.uri.path.toString())
+      val body = strictEntityBodyAsString(request.entity)
+      val withBody =
+        if (body.isEmpty) builder
+        else
+          builder
+            .withBody(body)
+            .withHeader("content-type", request.entity.contentType.value)
+      val withHeaders = request.headers.foldLeft(builder)((b, header) => b.withHeader(header.name, header.value))
+      val andQuery =
+        request.uri.query().foldLeft(withHeaders) { case (b, (key, value)) => b.withQueryParam(key, value) }
+      andQuery.build()
+    }
+
+    val specResponse = {
+      val builder = SimpleResponse.Builder
+        .status(response.status.intValue())
+      val body = strictEntityBodyAsString(response.entity)
+      val withBody =
+        if (body.isEmpty) builder
+        else
+          builder
+            .withBody(body)
+            .withHeader("content-type", response.entity.contentType.value)
+      val withHeaders = response.headers.foldLeft(builder)((b, header) => b.withHeader(header.name, header.value))
+      withHeaders.build()
+    }
+
+    specValidator
+      .validate(specRequest, specResponse)
+      .getMessages
+      .asScala
+      .filter(m => m.getLevel == ValidationReport.Level.ERROR)
+      .map(_.toString)
+  }
+
+  def strictEntityBodyAsString(entity: HttpEntity): String = entity match {
+    case s: HttpEntity.Strict => s.data.utf8String
+    case _                    => ""
+  }
+}
diff --git a/tests/src/test/scala/common/rest/WskRestOperations.scala b/tests/src/test/scala/common/rest/WskRestOperations.scala
index eefa782..85742cc 100644
--- a/tests/src/test/scala/common/rest/WskRestOperations.scala
+++ b/tests/src/test/scala/common/rest/WskRestOperations.scala
@@ -74,6 +74,7 @@ import java.nio.charset.StandardCharsets
 import java.security.KeyStore
 
 import akka.actor.ActorSystem
+import akka.util.ByteString
 import pureconfig.loadConfigOrThrow
 import whisk.common.Https.HttpsConfig
 
@@ -1132,10 +1133,11 @@ class RestGatewayOperations(implicit val actorSystem: ActorSystem) extends Gatew
   }
 }
 
-trait RunRestCmd extends Matchers with ScalaFutures {
+trait RunRestCmd extends Matchers with ScalaFutures with SwaggerValidator {
 
   val protocol = loadConfigOrThrow[String]("whisk.controller.protocol")
   val idleTimeout = 90 seconds
+  val toStrictTimeout = 5 seconds
   val queueSize = 10
   val maxOpenRequest = 1024
   val basePath = Path("/api/v1")
@@ -1187,8 +1189,17 @@ trait RunRestCmd extends Matchers with ScalaFutures {
       method,
       hostWithScheme.withPath(path).withQuery(Query(params)),
       List(Authorization(creds)),
-      entity = body.map(b => HttpEntity(ContentTypes.`application/json`, b)).getOrElse(HttpEntity.Empty))
-    Http().singleRequest(request, connectionContext).futureValue
+      entity =
+        body.map(b => HttpEntity.Strict(ContentTypes.`application/json`, ByteString(b))).getOrElse(HttpEntity.Empty))
+    val response = Http().singleRequest(request, connectionContext).flatMap { _.toStrict(toStrictTimeout) }.futureValue
+
+    val validationErrors = validateRequestAndResponse(request, response)
+    if (validationErrors.nonEmpty) {
+      fail(
+        s"HTTP request or response did not match the Swagger spec.\nRequest: $request\n" +
+          s"Response: $response\nValidation Error: $validationErrors")
+    }
+    response
   }
 
   private def getBasicHttpCredentials(wp: WskProps): BasicHttpCredentials = {
@@ -1280,7 +1291,7 @@ trait RunRestCmd extends Matchers with ScalaFutures {
   }
 
   def getRespData(resp: HttpResponse): String = {
-    val timeout = 5.seconds
+    val timeout = toStrictTimeout
     Try(resp.entity.toStrict(timeout).map { _.data }.map(_.utf8String).futureValue).getOrElse("")
   }
 
diff --git a/tools/travis/runTests.sh b/tools/travis/runTests.sh
index 9f53284..5d5b6d7 100755
--- a/tools/travis/runTests.sh
+++ b/tools/travis/runTests.sh
@@ -26,7 +26,7 @@ ROOTDIR="$SCRIPTDIR/../.."
 
 cd $ROOTDIR
 cat whisk.properties
-TERM=dumb ./gradlew :tests:testCoverageLean :tests:reportCoverage
+TERM=dumb ./gradlew :tests:testCoverageLean :tests:reportCoverage :tests:testSwaggerCodegen
 
 bash <(curl -s https://codecov.io/bash)
 echo "Time taken for ${0##*/} is $SECONDS secs"