Compare commits

...
Author SHA1 Message Date
Ta-Ching ChenandSoam Vasani 1b9fa5bc30 Unarchive zip file after fetcher downloads the package (#301)
Fetcher now uncompresses archive files. 

This is triggered by automatically detecting the file format, although maybe we should make that explicit or at least move the detection into the client.
2017-09-01 07:50:47 -07:00
Soam VasaniandGitHub 8d103fa35b Large functions: API proxy for storage svc, upload support in the CLI (#304)
This change contains CLI support for uploading large functions to the storage service.

It also adds a reverse proxy into the storage service to the fission API.

The helm chart is not yet updated to actually run the storage service -- that will come in the next change.
2017-08-31 15:42:59 -07:00
Ta-Ching ChenandSoam Vasani 9d1b096bac Add Environment v2 Builder (#298)
Build server and client for environments.

This build server will run inside environment builders and invoke environment-specific scripts for doing builds. It provides a uniform language-independent API for builds to the fission build manager.

The change also includes an implementation of a builder for Python -- this "builder" acts on requirements.txt and runs `pip install` to collect all function deps.
2017-08-31 10:56:40 -07:00
Soam VasaniandGitHub d83c89df69 Storage service and client (#300)
A very rudimentary storage service for archives larger than what can
fit in TPR/CRD resources. The library used allows for using local
files, AWS S3, or other cloud storage APIs. For now only local paths
are set up.

The storage service doesn't know anything about functions or packages;
it can be used for arbitrary archives.

There's no CLI integration yet.
2017-08-31 00:44:25 -07:00
Ta-Ching ChenandSoam Vasani c03f1a6d9c Add env builder & srcpkg through cli (#296)
CLI support for specifying an environment builder and source packages.
2017-08-28 10:44:31 -07:00
Liu YangandSoam Vasani 4fc55b3b3c Add link to the logs section of INSTALL.md (#299) 2017-08-28 09:19:18 -07:00
Soam VasaniandGitHub 6f017cf400 Split out the Package type into a first class Kubernetes resource (#295)
Split out the Package type into a first class Kubernetes resource. Before this change, packages were implicitly tied to functions.

This wasn't ideal because:
 * Functions will need to share packages
 * A package storage system may be more generally useful than just
   functions (for example, for storing static assets)

This change does the following:
* Updates the fission and tpr types to add a new Package and PackageSpec.  It also creates a PackageRef type, and a FunctionPackageRef type. The PackageRef simply references a package, but the FunctionPackageRef includes the name of a function within the package. This allows us to share packages between different functions.
* Updates fetcher and other components for first-class packages
* Allows customization of fetcher image pull policy in the helm charts
2017-08-25 13:40:45 -07:00
Soam Vasani b19be5c0a3 Reduce slack spam 2017-08-24 11:10:41 -07:00
Soam Vasani 9920008916 Project credits: link to Platform9 and contributor list 2017-08-23 11:25:09 -07:00
Soam Vasani 68ab7efcd4 Add slack notification token to travis 2017-08-23 08:11:29 -07:00
Soam VasaniandGitHub 3693082004 Helm chart bugfixes + end to end test bugfixes (#293)
Helm Chart fixes: bump chart version, parameterize image pull policy, pass poolmgr url to router.

E2E test: add a simple hello world test. Make test runner dump fission logs after test.
2017-08-23 07:00:49 -07:00
George BuckerfieldandSoam Vasani dd6bfd73e2 Minor documentation fix for the go example (#292) 2017-08-22 07:51:58 -07:00
rapitableandSoam Vasani fe9f200606 Improve error message if an older CLI attempts to make a request (#291)
Closes #290
2017-08-21 23:46:45 -07:00
Soam Vasani 3165fe7719 tpr description typo 2017-08-21 15:12:10 -07:00
Erwin van EykandSoam Vasani a4124abeb2 Update list of environments currently in README (#289) 2017-08-21 13:56:58 -07:00
Soam Vasani 789a7d25ce Empty commit to kick ci 2017-08-18 16:11:35 -07:00
Ta-Ching ChenandSoam Vasani 31f94dbc12 Set fetcher image through poolmgr env (#280)
Add a helm value for the fetcher image. Pass the fetcher image path to poolmgr through an env var.
2017-08-18 15:12:10 -07:00
Ta-Ching ChenandSoam Vasani 6bca24a3f5 Fix fetcher failed to access TPR if RBAC is enabled (#288) 2017-08-18 15:01:21 -07:00
Ta-Ching ChenandSoam Vasani 961ee069ed Set message content-type based on the trigger.Spec.ContentType (#279) 2017-08-17 10:30:24 -07:00
Soam Vasani b5e3e4e625 Scripts for a couple of simple fission demos 2017-08-16 15:18:31 -07:00
Soam VasaniandGitHub 00a8430a24 Fix bug that causes us to skip our new e2e tests (#285)
Various e2e test bugfixes. The test runner now compiles fission bundle and cli, installs helm, installs kubectl, and uses helm to install fission, run all scripts under test/tests/, and uninstalls fission.

The change also removes go tip tests -- I think we can get by without them, but we'll add them back if the next go version update brings some surprises.
2017-08-15 17:00:27 -07:00
Soam VasaniandGitHub f79d44156b End to end test runner (#281)
Build fission-bundle and fission cli. Install the fission-bundle using
the fission-all helm chart. Run all files matching test_*.sh from the
tests/ directory.

This change doesn't contain any real tests, just the build and test
runner scripts.

Skips the tests when there are no cluster credentials available.
2017-08-14 16:17:24 -07:00
Ta-Ching ChenandSoam Vasani a52425a31d Parse metadata.Name before creating tpr resource (#284) 2017-08-14 08:55:31 -07:00
Ta-Ching ChenandSoam Vasani 45f3095fb1 Remove etcd from fission-all chart (#282)
Since we use Kubernetes APIs for storing our state now, etcd can be removed.
2017-08-12 16:04:30 -07:00
Soam Vasani 96ae2d3d86 Update with our new codeofconduct@ alias 2017-08-11 17:36:53 -07:00
Soam Vasani 6f8a2e4789 Don't set KUBECONFIG when there is no reachable cluster
Travis doesn't pass secret env vars to tests run on forks, so we need
a way to skip the tests when there are no credentials passed in.

This change unsets KUBECONFIG when there's no reachable cluster. The
Go tests already check for KUBECONFIG and skip when it's not defined.
2017-08-11 15:02:14 -07:00
Soam VasaniandGitHub 00c2883235 Helm chart updates (#273)
Remove hardcoded "fission" namespace in various places.

Remove etcd deployment and service, since we use K8s APIs now.

Add ClusterRoleBinding for the fission service account, to allow fission components to access TPRs. We currently make the service account a cluster admin. This is more permissive than it should be; we should define a specific role for just fission resources.
2017-08-09 23:52:13 -07:00
Soam Vasani 42261d8810 Remove retries from poolmgr eagerPoolCreator
This was initially added to avoid restarts if poolmgr starts before
fission api; but we don't really need that since k8s will restart
it. Also, poolmgr now talks to the k8s api directly instead of the
fission api.
2017-08-09 22:37:55 -07:00
Soam VasaniandGitHub 7d83f5713a Kubernetes access for Travis CI tests (#272)
Sets up Travis CI tests to use a Kubernetes cluster on GKE. All tests have access to $KUBECONFIG which points at a kubeconfig file with credentials to a cluster.

I had to skip the poolmgr unit test because it currently assumes NodePort services; that will be fixed in another change.

Also, because some of the tests don't work properly in parallel, I've ended up reducing the concurrency to 1. If this becomes a problem we'll have to fix the tests, but for now I don't mind just waiting a few minutes longer.
2017-08-08 17:01:34 -07:00
Soam VasaniandGitHub e238776bf7 V2 types and TPR (#266)
This changes the core fission function, environment and trigger types. It also changes Fission's storage to use ThirdPartyResources.

 - Functions are now specified by packages. Functions can also have both source and deployment packages. A package can be specified by a literal, or by a URL.
 - Environments have a build and runtime component.
 - Triggers reference functions by a FunctionReference. This is a layer of indirection between triggers and functions, and will allow things like incremental function upgrades in future releases.

See Documentation/wip/env-v2.md for design discussion about points 1 and 2.

Changes:

* V2 Types

All types now have a spec, following the pattern of K8s objects.

Functions now have source and deployment packages. A Package can be
specified by literal, or by URL.

Environments now have a builder and runtime component.

All triggers use a new FunctionReference to specify the function. This
for now only uses a function name, but in the future can be extended
to be more flexible.

A new FunctionLoadRequest type is added for specialization requests to
the environment runtime.

* TPR types, TPR init code, and a "fission client"

Implements TPR types using the spec types in fission/types.go.

Adds code for adding creating TPR types, and convenient types for crud
operations on each of our resource types.

Adds code for connecting to K8s API and configuring a REST client with
fission types set up.

* Change old stateful controller into a thin apiserver

This apiserver is now simply a stateless api layer on top of the TPR
types. At the moment it doesn't do anything that couldn't be done by
simply talking to the TPR types. In the future we can have better
validation and potentially some higher level APIs (like versioning for
example) in here.

* Split controller client into files and update for v2 types.

* Update CLI for v2 types.

As far as possible we keep the CLI flags the same. We'll have to add
flags for source/deploy packages and builder/runtime
environments. That will come in the next change.

* Update poolmgr and fetcher for v2 types.

Also adds a poolmgr_test.

* Update router for new types.

Also adds a function reference resolver, which separates out the job
of resolving a FunctionReference to a function.

* Update kubewatcher and timer for v2 types.

* Update Message Queue trigger type for v2 types.

* Minor odds and ends.

* Fission bundle CLI updates

Remove controllerUrl flag, since we don't need it any more.

* Remove etcd deployment (replaced by storing state in TPR)

Also update the poolmgr commandline, and use an env var for the
fetcher image URL.

* Explicit ChecksumType and consts

* Clarify separation of environment interface types
2017-08-05 01:18:30 -07:00
Ta-Ching ChenandSoam Vasani 37aa266a4d Update/Add fission-core & fission-all helm charts (#239)
Improve fission helm chart; split it into "fission core" and "fission all". fission-core is the minimal install, and fission-all will have all the bells and whistles.

* Update/Add fission-core & fission-all helm charts
* Fix incompatible types for comparison
* Add message queue trigger
* Rename openshiftRBAC to openshift
* Update documentation & comments
* Fix logger & kubewatcher failed to access kubernetes resource due to RBAC
2017-07-31 14:10:26 -07:00
Erwin van EykandSoam Vasani 85983c91cd Binary Environment (#256)
Adds a fission environment for arbitrary linux binaries.
2017-07-26 12:29:54 -07:00
Ta-Ching ChenandSoam Vasani a8fc7a7029 Fix nats trigger replies message to non-existing resptopic (#260) 2017-07-26 11:43:23 -07:00
Ta-Ching ChenandSoam Vasani 2036d80778 Fix logger prints wrong log (#263) 2017-07-20 13:44:23 -07:00
Ta-Ching ChenandSoam Vasani 46ff55ab49 Fix unstoppable kubewatcher (#208)
Fix a bug due to which watches could never be stopped; simplify control flow a bit.
2017-07-10 16:01:48 -07:00
S. Brent FaulknerandSoam Vasani 51c84f3cfc Ruby logger (#251)
* Add logger for ruby environment

* Update readme and example for ruby logger

* ruby-env: log load time for user code
2017-07-07 18:05:11 -07:00
S. Brent FaulknerandSoam Vasani fe4015fab2 fix typo funtion -> function (#252) 2017-07-07 09:37:35 -07:00
Soam Vasani 22e0c10d07 Update install guide links to latest release 2017-07-05 17:49:40 -07:00
Ta-Ching ChenandSoam Vasani e4fe007838 Add message queue trigger support (#218)
Adds support for message queue triggers based on the NATS-Streaming message queue.

This lets the user map a queue topic to a function, and optionally send the function's response into another queue topic.

The message queue trigger manager is designed to be message queue independent, so we should be able add support for more queues by adding implementations for the relatively simple mqtrigger.MessageQueue interface.
2017-07-05 17:19:58 -07:00
S. Brent FaulknerandSoam Vasani 0fd60eab3b include path parameters in params hash for ruby environment (#249)
* include path parameters in params hash for ruby environment

* Improve code organization and documentation for ruby environment.

* Document Ruby examples
2017-07-04 23:44:18 -07:00
Soam Vasani ae87c0ea37 Fix curl command in install instructions (#248) 2017-07-03 16:29:09 -07:00
Soam Vasani 55651d49e0 Environment v2 interface doc (wip) 2017-06-30 09:23:35 -07:00
Soam Vasani 1f0ca47550 release helper script 2017-06-28 15:50:25 -07:00
Soam Vasani e619a70661 add sections to cli download 2017-06-28 15:07:23 -07:00
Soam Vasani d248bb89ea Install docs improvements
Keep install docs in one place INSTALL.md instead of two.

Use github releases for all YAMLs and CLIs. This has a few advantages:
 (a) it makes releasess more transparent
 (b) all yaml/cli files are transferred over HTTPS
 (c) CLIs and YAMLs being from the same release ensures that people
     always get compatible versions (we will still occasionally break
     api compatibility, until beta)
2017-06-28 15:00:42 -07:00
Ta-Ching ChenandSoam Vasani 5fe4b276eb Fission update must require at least one change to function (#241) 2017-06-26 22:22:09 -07:00
172 changed files with 10484 additions and 3872 deletions
+1
View File
@@ -10,3 +10,4 @@ environments/php7/vendor/
*~
vendor/
local/
+25 -11
View File
@@ -1,20 +1,34 @@
sudo: required
dist: trusty
language: go
go:
- 1.8
- tip
- 1.8
cache:
directories:
- $HOME/google-cloud-sdk/
- $HOME/k8scli
install:
- go get github.com/Masterminds/glide
- curl -L https://github.com/coreos/etcd/releases/download/v3.0.15/etcd-v3.0.15-linux-amd64.tar.gz -o /tmp/etcd-v3.0.15-linux-amd64.tar.gz && mkdir -p /tmp/test-etcd && tar xzvf /tmp/etcd-v3.0.15-linux-amd64.tar.gz -C /tmp/test-etcd --strip-components=1
- go get github.com/Masterminds/glide
- hack/travis-kube-setup.sh
env:
- KUBECONFIG=${HOME}/.kube/config PATH=$HOME/k8scli:${PATH}
script:
- cd ${TRAVIS_BUILD_DIR}
- glide install
- ./fission-bundle/build.sh
- hack/verify-gofmt.sh
- /tmp/test-etcd/etcd &
- go test -v -i $(go list ./... | grep -v '/vendor/' | grep -v 'examples/go')
- go test -v $(go list ./... | grep -v '/vendor/' | grep -v 'examples/go')
- cd ${TRAVIS_BUILD_DIR}
- glide install
- ./fission-bundle/build.sh
- hack/verify-gofmt.sh
- hack/runtests.sh
- test/build_and_test.sh
notifications:
slack:
secure: YZ34vsfw1TtftJypg1MyP4+ihONI4gaxeS3FghBQli6+EezjzcxOXyj5VD+x0ucXfDeTaDrFfmVN0SAGObOoZUE+ea9KAoTo50tRLaD9kwOTACiekZalC4uuBguH0D1/A6vlbU8dchsr9mvIhbisG6mTncdPtqGYYHtyBQme6ngmmHbVAQFZcIBHmNuDb/HWhSr8KMEuyB6+mBXLYELHnXnf26cOhdGNaagqCOJTiemX85RGwIuOPxyBhKDFMLyHohDT7FJMH/qijveE6YgOYTQC5nYc2Np1KvC7hQkIu4nuyczyYlrNQl/TWv+SVI8PjIs0PYuCuD3gUqoVEi8d94HbrOPzFEpbwDS9P4qL39DGmco1Q56Vqxe6sRI0vDWPb5gCP1lSgs3PMECVn7Wor/pTvcL+C+U2jLwWJUl0vbyWCL7ngl/3iTssV7qBpUrI7Oclwp8LrQo9fPj0DL4gE9rNanpEWjQ6yPGaysIL1zLHtRghhm52A22NJGp71jkS2KEpLi6ZWFYjMeuXw5eOQFhqFlzyRJOmLYa3B607TLWuyo2L2CxAfMmq0FGfemvrkLZIWtlQKK4y9ImpsURwaGT2XCtThFtHl77wEss913nC+T2dX3O5Bl0UmxFd5S3mVM109I8c4lDosxnAjRfS9MheFlrG0gjSJSBCw57x7f0=
on_success: change
on_failure: always
+1 -1
View File
@@ -55,7 +55,7 @@ further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at soamvasani@platform9.com. All
reported by contacting the project team at codeofconduct@platform9.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
+55
View File
@@ -0,0 +1,55 @@
Compiling Fission
=================
[You only need to do this if you're making Fission changes; if you're
just deploying Fission, use fission.yaml which points to prebuilt
images.]
You'll need the `go` compiler and tools installed, along with the
[glide dependency management
tool](https://github.com/Masterminds/glide#install). You'll also need
docker for building images.
The server side is compiled as one binary ("fission-bundle") which
contains controller, poolmgr and router; it invokes the right one
based on command-line arguments.
To build fission-bundle: clone this repo to
`$GOPATH/src/github.com/fission/fission`, then from the top level
directory (if you want to build the image with the docker inside
minikube, you'll need to set the proper environment variables with
`eval $(minikube docker-env)`):
```
# Get dependencies
$ glide install
# Build fission server and an image
$ pushd fission-bundle
$ ./build.sh
```
You now need to build the docker image for fission. You can use
`push.sh` and push it to a docker hub account. But it's easiest to use
minikube and its built-in docker daemon:
```
$ eval $(minikube docker-env)
$ docker build -t minikube/fission-bundle .
```
Next, install fission on your kubernetes cluster:
```
# To install, update fission.yaml to point to the image you just made: "minikube/fission-bundle"
$ $EDITOR fission.yaml
$ kubectl create -f fission.yaml
$ kubectl create -f fission-nodeport.yaml
```
And if you're changing the CLI too, you can build it with:
```
# Build Fission CLI
$ cd fission && go install
```
+14 -1
View File
@@ -95,6 +95,19 @@ While a few simple retries are done, there isn't yet a reliable
message bus between Kubewatcher and the function. Work for this is
tracked in issue #64.
Message Queue Trigger
---------------------
A message queue trigger binds a message queue topic to a function:
events from that topic cause the function to be invoked with the
message as the body of the request. The trigger may also contain a
response topic: if specified, the function's output is sent to this
response.
Here's a diagram of the components:
![Message queue trigger Diagram](https://user-images.githubusercontent.com/202578/27012344-9457cb24-4f00-11e7-8d6b-926ff01637b3.jpg)
Environment Container
---------------------
@@ -113,7 +126,7 @@ volume shared between fetcher and this environment container. Poolmgr
then requests the container to load the function.
Logger
-----------
------
Logger helps to forward function logs to centralized db service for log
persistence. Currently only influxdb is supported to store logs.
+120
View File
@@ -0,0 +1,120 @@
# Environment V2 Fission-Environment API
Fission Environments are the language-specific component of fission.
They must satisfy the interface in this spec.
## Meta
This is version 2.0-alpha of the Fission-Environment API.
(It's unstable and may change without warning until 2.0-beta.)
## Overview
Fission V2 Environments consist of:
* Metadata
* A runtime image
* A builder image (optional)
* Function Interface Specification
* User Documentation
* Examples
### Metadata
See EnvironmentSpec, Runtime, and Builder in types.go.
## Runtime Image
An environment runtime image is a docker container image. It must run
a server that has two jobs:
(a) Loading a "function" from a file path on demand
(b) Invoking that function on request
### Function Loading
The environment must expose a single HTTP endpoint (at the port and
URL specified in the metadata) that loads a function. The function
load request is a JSON-serialized `FunctionLoadRequest`.
The function load request contains a filepath to load the function
from. Fission does not define in any way the contents of the path; it
is completely environment-dependent. It may be a single file, or a
directory (in case of deployment packages).
The load request may contain an EntryPoint. If it does, the loader
must interpret this; usually it's the name of a function in a module
or package containing multiple functions. If there is no entrypoint,
the environment must use a default; again, the value of thsi default
is environment-specific.
The load request may contain a URL. If it does, requests to that URL
should be routed to the function. It defaults to "/".
### Function Invocation
Functions are invoked on HTTP request to the server. The port for the
request on the runtime container is defined in the Runtime metadata,
and the URL for the request is specified in the FunctionLoadRequest.
The interface of the function is environment specific; the environment
must come with a spec for this interface.
## Builder
The builder is a container image that contains tools to build a
function from source. The source may be a single file or a directory
of files.
The builder container is invoked with the specified command, with the
following params:
1. File path of the source
2. File path where the output should go
3. Other env or function-specific params passed by the user
The first two parameters are file paths, and all remaining params are
environment-specific.
The output of the builder should be something that the runtime can
load and run -- there should be no intermediate steps that need user
intervention.
### Errors
## Function Interface Spec
The function interface spec is a document that specifies the interface
of functions and their semantics. It must specify:
* How functions are invoked (sync, async)
* How the request context is provided to the function (URL, headers, request type, request body)
* Function logging
* Semantics of function errors and exceptions
## Documentation
The docs should contain everything necessary to use the environment:
* How to add it to a fission cluster
* How to write and build functions for this environment (link to the interface spec)
* How to modify and rebuild the environment itself
## Examples
Suggested examples to provide:
* A simple "Hello world"
* A function that demonstrates use of the request context: url params,
request headers, request body
* A function that does logging
* A multi-file function package
* A function with dependencies
* Functions with shared code
## Compatibility with v1
V1 environment images can be used as v2 environment runtime images.
+75 -22
View File
@@ -1,14 +1,18 @@
* [Running Fission on your Cluster](#running-fission-on-your-cluster)
* [Setup Kubernetes](#setup-kubernetes)
* [Mac](#install-and-start-kubernetes-on-osx)
* [Linux](#or-install-and-start-kubernetes-on-linux)
* [Verify access to the cluster](#verify-access-to-the-cluster)
* [Get and Run Fission: Minikube or Local cluster](#get-and-run-fission-minikube-or-local-cluster)
* [Get and Run Fission: GKE or other Cloud](#get-and-run-fission-gke-or-other-cloud)
* [Install the client CLI](#install-the-client-cli)
* [Run an example](#run-an-example)
* [Enable Persistent Function Logs (Optional)](#enable-persistent-function-logs-optional)
* [Use the web based Fission-ui (Optional)](#use-the-web-based-fission-ui-optional)
- [Running Fission on your Cluster](#running-fission-on-your-cluster)
* [Setup Kubernetes](#setup-kubernetes)
+ [Install and start Kubernetes on OSX:](#install-and-start-kubernetes-on-osx)
+ [Or, install and start Kubernetes on Linux:](#or-install-and-start-kubernetes-on-linux)
* [Verify access to the cluster](#verify-access-to-the-cluster)
* [Get and Run Fission: Minikube or Local cluster](#get-and-run-fission-minikube-or-local-cluster)
* [Get and Run Fission: GKE or other Cloud](#get-and-run-fission-gke-or-other-cloud)
* [Get and Run Fission: OpenShift](#get-and-run-fission-openshift)
+ [Using Minishift or Local Cluster](#using-minishift-or-local-cluster)
+ [Using other clouds](#using-other-clouds)
* [Install the client CLI](#install-the-client-cli)
* [Run an example](#run-an-example)
* [Enable Persistent Function Logs (Optional)](#enable-persistent-function-logs-optional)
* [Use the web based Fission-ui (Optional)](#use-the-web-based-fission-ui-optional)
## Running Fission on your Cluster
@@ -45,8 +49,8 @@ set up services with NodePort. This exposes fission on ports 31313
and 31314.
```
$ kubectl create -f http://fission.io/fission.yaml
$ kubectl create -f http://fission.io/fission-nodeport.yaml
$ kubectl create -f https://github.com/fission/fission/releases/download/nightly20170705/fission-rbac.yaml
$ kubectl create -f https://github.com/fission/fission/releases/download/nightly20170705/fission-nodeport.yaml
```
Set the FISSION_URL and FISSION_ROUTER environment variables.
@@ -68,8 +72,8 @@ If you're using GKE or any other cloud provider that supports the
LoadBalancer service type, use these commands:
```
$ kubectl create -f http://fission.io/fission.yaml
$ kubectl create -f http://fission.io/fission-cloud.yaml
$ kubectl create -f https://github.com/fission/fission/releases/download/nightly20170705/fission-rbac.yaml
$ kubectl create -f https://github.com/fission/fission/releases/download/nightly20170705/fission-cloud.yaml
```
Save the external IP addresses of controller and router services in
@@ -84,8 +88,11 @@ svc```). Then:
### Get and Run Fission: OpenShift
If you're using OpenShift, it's possible to run Fission on it! The deployment
template needs to be deployed as a user with cluster-admin permissions (like `system:admin`), as it needs to create a `ClusterRole` for deploying function containers from the `fission` namespace/project.
If you're using OpenShift, it's possible to run Fission on it! The
deployment template needs to be deployed as a user with cluster-admin
permissions (like `system:admin`), as it needs to create a
`ClusterRole` for deploying function containers from the `fission`
namespace/project.
Identically as with Kubernetes, you need to set the FISSION_URL and FISSION_ROUTER environment variables. If you're using minishift, use these commands:
@@ -93,21 +100,48 @@ Identically as with Kubernetes, you need to set the FISSION_URL and FISSION_ROUT
$ export FISSION_URL=http://$(minishift ip):31313¬
$ export FISSION_ROUTER=$(minishift ip):31314¬
```
#### Using Minishift or Local Cluster
If you're using minishift or no cloud provider, use these commands to set up services with NodePort. This exposes fission on ports 31313 and 31314.
```
$ oc login -u system:admin
$ oc create -f https://github.com/fission/fission/releases/download/nightly20170705/fission-openshift.yaml
$ oc create -f https://github.com/fission/fission/releases/download/nightly20170705/fission-nodeport.yaml
```
#### Using other clouds
If you're using any cloud provider that supports the LoadBalancer service type, use these commands:
```
$ oc login -u system:admin
$ oc create -f https://github.com/fission/fission/releases/download/nightly20170705/fission-openshift.yaml
$ oc create -f https://github.com/fission/fission/releases/download/nightly20170705/fission-cloud.yaml
```
After these steps, you should be able to run fission client as with kubernetes.
### Install the client CLI
#### Mac OS
Get the CLI binary for Mac:
```
$ curl http://fission.io/mac/fission > fission && chmod +x fission && sudo mv fission /usr/local/bin/
$ curl -Lo fission https://github.com/fission/fission/releases/download/nightly20170705/fission-cli-osx && chmod +x fission && sudo mv fission /usr/local/bin/
```
Or Linux:
#### Linux
```
$ curl http://fission.io/linux/fission > fission && chmod +x fission && sudo mv fission /usr/local/bin/
$ curl -Lo fission https://github.com/fission/fission/releases/download/nightly20170705/fission-cli-linux && chmod +x fission && sudo mv fission /usr/local/bin/
```
#### Windows
For Windows, you can use the linux binary on WSL. Or you can download
this windows executable: [fission.exe](https://github.com/fission/fission/releases/download/nightly20170705/fission-cli-windows.exe)
### Run an example
Finally, you're ready to use Fission!
@@ -132,7 +166,7 @@ Fission uses InfluxDB to store logs and fluentd to forward them from
function pods into InfluxDB.
```
$ kubectl create -f fission-logger.yaml
$ kubectl create -f https://github.com/fission/fission/releases/download/nightly20170705/fission-logger.yaml
```
That's it for the basic setup. You can now use following command to view function logs:
@@ -159,10 +193,29 @@ It allows users to observe and manage fission. It also provides a simple online
To setup Fission-ui with fission in k8s is simple:
```bash
# After Fission deployed
# Run this after fission is deployed
$ kubectl create -f https://raw.githubusercontent.com/fission/fission-ui/master/docker/fission-ui.yaml
```
Then open `http://node-ip:31319` to use Fission-ui.
For more infomation, please check out [Fission-ui Readme](https://github.com/fission/fission-ui/blob/master/README.md).
### Install NATS for message-queue based triggers (Optional)
Fission supports message queue triggers that allow you to invoke
functions based on events in a queue. For now, NATS-Streaming is the
only supported message queue.
You can install NATS Streaming on your Kubernetes cluster with:
```
$ kubectl create -f https://github.com/fission/fission/releases/download/nightly20170705/fission-nats.yaml
```
You can subscribe to a NATS Streaming queue with a command like this:
(See `fission mqtrigger --help` for details)
```
$ fission mqtrigger create --name myQueueTrigger --function processEvent --topic "myQueue.request"
```
+7 -183
View File
@@ -15,8 +15,8 @@ extend Fission if you want to.
Fission is extensible to any language; the core is written in Go, and
language-specific parts are isolated in something called
_environments_ (more below). Fission currently supports NodeJS and
Python, with more languages coming soon.
_environments_ (more below). Fission currently supports NodeJS, Python, Ruby, Go,
PHP, Bash, and any Linux executable, with more languages coming soon.
### Performance: 100msec cold start
@@ -81,187 +81,12 @@ See the [examples](examples) directory for more.
Running Fission on your Cluster
===============================
### Setup Kubernetes
You can install Kubernetes on your laptop with [minikube](https://github.com/kubernetes/minikube):
#### Install and start Kubernetes on OSX:
```bash
$ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin
$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.16.0/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
$ minikube start
```
#### Or, install and start Kubernetes on Linux:
```bash
$ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin
$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.16.0/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
$ minikube start
```
Or, you can use [Google Container Engine's](https://cloud.google.com/container-engine/) free trial to get a 3 node cluster.
### Verify access to the cluster
```
$ kubectl version
```
### Get and Run Fission: Minikube or Local cluster
If you're using minikube or no cloud provider, use these commands to
set up services with NodePort. This exposes fission on ports 31313
and 31314.
```
$ kubectl create -f http://fission.io/fission.yaml
$ kubectl create -f http://fission.io/fission-nodeport.yaml
```
Set the FISSION_URL and FISSION_ROUTER environment variables.
FISSION_URL is used by the fission CLI to find the server.
FISSION_URL should be prefixed with a `http://`. (FISSION_ROUTER is
only needed for the examples below to work.)
If you're using minikube, use these commands:
```
$ export FISSION_URL=http://$(minikube ip):31313
$ export FISSION_ROUTER=$(minikube ip):31314
```
### Get and Run Fission: GKE or other Cloud
If you're using GKE or any other cloud provider that supports the
LoadBalancer service type, use these commands:
```
$ kubectl create -f http://fission.io/fission.yaml
$ kubectl create -f http://fission.io/fission-cloud.yaml
```
Save the external IP addresses of controller and router services in
FISSION_URL and FISSION_ROUTER, respectively. Wait for services to
get IP addresses (check this with ```kubectl --namespace fission get
svc```). Then:
```
$ export FISSION_URL=http://$(kubectl --namespace fission get svc controller -o=jsonpath='{..ip}')
$ export FISSION_ROUTER=$(kubectl --namespace fission get svc router -o=jsonpath='{..ip}')
```
### Get and run fission: OpenShift
If you're using OpenShift, it's possible to run Fission on it! The deployment
template needs to be deployed as a user with cluster-admin permissions (like `system:admin`), as it needs to create a `ClusterRole` for deploying function containers from the `fission` namespace/project.
Identically as with Kubernetes, you need to set the FISSION_URL and FISSION_ROUTER environment variables. If you're using minishift, use these commands:
```
$ export FISSION_URL=http://$(minishift ip):31313¬
$ export FISSION_ROUTER=$(minishift ip):31314¬
```
#### Using Minishift or Local Cluster
If you're using minishift or no cloud provider, use these commands to set up services with NodePort. This exposes fission on ports 31313 and 31314.
```
$ oc login -u system:admin
$ oc create -f http://fission.io/fission-openshift.yaml
$ oc create -f http://fission.io/fission-nodeport.yaml
```
#### Using other clouds
If you're using any cloud provider that supports the LoadBalancer service type, use these commands:
```
$ oc login -u system:admin
$ oc create -f http://fission.io/fission-openshift.yaml
$ oc create -f http://fission.io/fission-cloud.yaml
```
After these steps, you should be able to run fission client as with kubernetes.
### Install the client CLI
Get the CLI binary for Mac:
```
$ curl http://fission.io/mac/fission > fission && chmod +x fission && sudo mv fission /usr/local/bin/
```
Or Linux:
```
$ curl http://fission.io/linux/fission > fission && chmod +x fission && sudo mv fission /usr/local/bin/
```
### Run an example
Finally, you're ready to use Fission!
```
$ fission env create --name nodejs --image fission/node-env
$ curl https://raw.githubusercontent.com/fission/fission/master/examples/nodejs/hello.js > hello.js
$ fission function create --name hello --env nodejs --code hello.js
$ fission route create --method GET --url /hello --function hello
$ curl http://$FISSION_ROUTER/hello
Hello, world!
```
You can also set up persistence for logs: [instructions here](INSTALL.md).
See the [installation guide](INSTALL.md).
Compiling Fission
=================
[You only need to do this if you're making Fission changes; if you're
just deploying Fission, use fission.yaml which points to prebuilt
images.]
You'll need go installed, along with the [glide dependency management
tool](https://github.com/Masterminds/glide#install).
You'll also need docker for building images.
The server side is compiled as one binary ("fission-bundle") which
contains controller, poolmgr and router; it invokes the right one
based on command-line arguments.
To build fission-bundle: clone this repo to
`$GOPATH/src/github.com/fission/fission`, then from the top level
directory (if you want to build the image with the docker inside
minikube, you'll need to set the proper environment variables with
`eval $(minikube docker-env)`):
```
# Get dependencies
$ glide install
# Build fission server and an image
$ pushd fission-bundle
$ ./build.sh
# Edit push.sh to point to your registry, or comment out the `docker push`
# line if building into your local minikube for dev purposes
$ $EDITOR push.sh
$ ./push.sh
$ popd
# To install, update fission.yaml to point to your compiled image
$ $EDITOR fission.yaml
$ kubectl create -f fission.yaml
```
If you're changing the CLI:
```
# Build Fission CLI
$ cd fission && go install
```
See the [compilation guide](Compiling.md).
Status
======
@@ -269,9 +94,8 @@ Status
Fission is in early alpha. It's not suitable for production use just
yet.
We're looking for early developer feedback -- if you do use Fission,
we'd love to hear how it's working for you, what parts in particular
you'd like to see improved, and so on. Talk to us on
[slack](http://slack.fission.io) or
Reach us on [slack](http://slack.fission.io) or
[twitter](https://twitter.com/fissionio).
Fission is a project by [Platform9 Systems](https://platform9.com/fission/)
and [many contributors](https://github.com/fission/fission/graphs/contributors).
+166
View File
@@ -0,0 +1,166 @@
/*
Copyright 2016 The Fission Authors.
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 builder
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/dchest/uniuri"
)
const (
// supported environment variables
envSrcPkg = "SRC_PKG"
envDeployPkg = "DEPLOY_PKG"
)
type (
PackageBuildRequest struct {
SrcPkgFilename string `json:"srcPkgFilename"`
// Command for builder to run with.
// A build command consists of commands, parameters and environment variables.
// For now, two environment variables are supported:
// 1. SRC_PKG: path to source package directory
// 2. DEPLOY_PKG: path to deployment package directory
BuildCommand string `json:"command"`
}
PackageBuildResponse struct {
ArtifactFilename string `json:"artifactFilename"`
BuildLogs string `json:"buildLogs"`
}
Builder struct {
sharedVolumePath string
}
)
func MakeBuilder(sharedVolumePath string) *Builder {
return &Builder{
sharedVolumePath: sharedVolumePath,
}
}
func (builder *Builder) Handler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "", 405)
return
}
startTime := time.Now()
defer func() {
elapsed := time.Now().Sub(startTime)
log.Printf("elapsed time in build request = %v", elapsed)
}()
// parse request
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Error reading request body")
http.Error(w, err.Error(), 500)
return
}
var req PackageBuildRequest
err = json.Unmarshal(body, &req)
if err != nil {
log.Printf("Error parsing json body: %v", err)
http.Error(w, err.Error(), 400)
return
}
log.Printf("Builder received request: %v", req)
log.Println("Starting build...")
srcPkgPath := filepath.Join(builder.sharedVolumePath, req.SrcPkgFilename)
deployPkgFilename := fmt.Sprintf("%v-%v", req.SrcPkgFilename, strings.ToLower(uniuri.NewLen(6)))
deployPkgPath := filepath.Join(builder.sharedVolumePath, deployPkgFilename)
buildLogs, err := builder.build(req.BuildCommand, srcPkgPath, deployPkgPath)
if err != nil {
e := errors.New(fmt.Sprintf("Error building source package: %v", err))
http.Error(w, e.Error(), 500)
return
}
resp := PackageBuildResponse{
ArtifactFilename: deployPkgFilename,
BuildLogs: buildLogs,
}
rBody, err := json.Marshal(resp)
if err != nil {
e := errors.New(fmt.Sprintf("Error encoding response body: %v", err))
http.Error(w, e.Error(), 500)
return
}
w.Header().Add("Content-Type", "application/json")
w.Write(rBody)
w.WriteHeader(http.StatusOK)
}
func (builder *Builder) build(command string, srcPkgPath string, deployPkgPath string) (string, error) {
cmd := exec.Command(command)
cmd.Dir = srcPkgPath
// set env variables for build command
cmd.Env = append(os.Environ(),
fmt.Sprintf("%v=%v", envSrcPkg, srcPkgPath),
fmt.Sprintf("%v=%v", envDeployPkg, deployPkgPath),
)
cmdReader, err := cmd.StdoutPipe()
if err != nil {
return "", errors.New(fmt.Sprintf("Error creating stdout pipe for cmd: %v", err.Error()))
}
scanner := bufio.NewScanner(cmdReader)
err = cmd.Start()
if err != nil {
return "", errors.New(fmt.Sprintf("Error starting cmd: %v", err.Error()))
}
var buildLogs string
fmt.Println("\n=== Build Logs ===")
for scanner.Scan() {
output := scanner.Text()
fmt.Println(output)
buildLogs += fmt.Sprintf("%v\n", output)
}
fmt.Println("==================\n")
if err := scanner.Err(); err != nil {
return "", errors.New(fmt.Sprintf("Error reading cmd output: %v", err.Error()))
}
err = cmd.Wait()
if err != nil {
return "", errors.New(fmt.Sprintf("Error waiting for cmd: %v", err.Error()))
}
return buildLogs, nil
}
+56
View File
@@ -0,0 +1,56 @@
/*
Copyright 2016 The Fission Authors.
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 client
import (
"bytes"
"encoding/json"
"net/http"
"strings"
"github.com/fission/fission"
builder "github.com/fission/fission/builder"
)
type (
Client struct {
url string
}
)
func MakeClient(serverUrl string) *Client {
return &Client{
url: strings.TrimSuffix(serverUrl, "/"),
}
}
func (c *Client) Build(req *builder.PackageBuildRequest) error {
body, err := json.Marshal(req)
if err != nil {
return err
}
resp, err := http.Post(c.url, "application/json", bytes.NewReader(body))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return fission.MakeErrorFromHTTP(resp)
}
return nil
}
+5
View File
@@ -0,0 +1,5 @@
FROM alpine:3.4
ADD builder /
EXPOSE 8000
+2
View File
@@ -0,0 +1,2 @@
#!/bin/sh
GOOS=linux GOARCH=386 go build -o builder .
+22 -17
View File
@@ -14,24 +14,29 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package fission
package main
func (f Function) Key() string {
return f.Metadata.Name
}
import (
"log"
"net/http"
"os"
func (e Environment) Key() string {
return e.Metadata.Name
}
builder "github.com/fission/fission/builder"
)
func (ht HTTPTrigger) Key() string {
return ht.Metadata.Name
}
func (tt TimeTrigger) Key() string {
return tt.Metadata.Name
}
func (w Watch) Key() string {
return w.Metadata.Name
// Usage: builder <shared volume path>
func main() {
dir := os.Args[1]
if _, err := os.Stat(dir); err != nil {
if os.IsNotExist(err) {
err = os.MkdirAll(dir, os.ModeDir|0700)
if err != nil {
log.Fatalf("Error creating directory: %v", err)
}
}
}
builder := builder.MakeBuilder(dir)
mux := http.NewServeMux()
mux.HandleFunc("/", builder.Handler)
http.ListenAndServe(":8000", mux)
}
+82
View File
@@ -0,0 +1,82 @@
# Fission
[Fission](http://fission.io/) is a framework for serverless functions on Kubernetes.
## Prerequisites
- Kubernetes 1.6+ with Beta APIs enabled
## Helm charts
The following table lists two helm charts for Fission.
| Parameter | Description |
| ---------------| ---------------------------------------------------------------------------------------|
| `fission-core` | FaaS essentials, and triggers for HTTP, Timers and Kubernetes Watches |
| `fission-all` | Log aggregation with fluentd and InfluxDB; NATS for message queue triggers; Fission-UI |
## Installing the chart
To install the chart with the release name `my-release`,
```bash
$ helm install --name my-release fission-all
```
## Uninstalling the chart
To uninstall/delete chart,
```bash
$ helm delete my-release
```
## Configuration
The following table lists the configurable parameters of the Fission chart and their default values.
| Parameter | Description | Default |
| ------------------- | ------------------------------------------ | ------------------------ |
| `serviceType` | Type of service to use | `LoadBalancer`. |
| `image` | Fission image | `fission/fission-bundle` |
| `imageTag` | Fission image tag | `alpha20170124` |
| `fetcherImage` | Fission fetcher image | `fission/fetcher` |
| `fetcherImageTag` | Fission fetcher image tag | `latest` |
| `controllerPort` | Fission Controller Service Port | `31313` |
| `routerPort` | Fission Router Service Port | `31314` |
| `functionNamespace` | Namespace for Fission functions | `fission-function` |
| `openshift` | RBAC for openshift | `false` |
* Extra configuration for `fission-all`
| Parameter | Description | Default |
| ---------------------- | --------------------------- | -------------------------- |
| `logger.influxdbAdmin` | Log database admin username | `admin`. |
| `logger.fluentdImage` | Logger fluentd image | `fission/fluentd` |
| `fissionUiImage` | Fission ui image | `fission/fission-ui:0.1.0` |
| `nats.authToken` | Nats streaming auth token | `defaultFissionAuthToken` |
| `nats.clusterID` | Nats streaming clusterID | `fissionMQTrigger` |
Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example,
```bash
$ helm install --name my-release --set image=custom/fission-bundle,imageTag=v1 fission-all
```
If you're using minikube, set serviceType to NodePort:
```bash
$ helm install --name my-release --set serviceType=NodePort fission-all
```
You can also set parameters with a yaml file (see values.yaml for
what it should look like):
```bash
$ helm install --name my-release -f values.yaml fission-all
```
+16
View File
@@ -0,0 +1,16 @@
apiVersion: v1
name: fission-all
version: v0.2.0-20170822
description: Fission is a fast serverless framework for Kubernetes.
keywords:
- fission
- serverless
home: http://fission.io/
maintainers:
- name: Sanket Sudake
email: sanketsudake@gmail.com
- name: Soam Vasani
email: soamvasani@platform9.com
- name: Ta Ching Chen
email: contact@tachingchen.com
engine: gotpl
@@ -1,10 +1,13 @@
1. Install the client CLI.
Mac:
$ curl http://fission.io/mac/fission > fission && chmod +x fission && sudo mv fission /usr/local/bin/
$ curl -Lo fission https://github.com/fission/fission/releases/download/nightly20170705/fission-cli-osx && chmod +x fission && sudo mv fission /usr/local/bin/
Linux:
$ curl http://fission.io/linux/fission > fission && chmod +x fission && sudo mv fission /usr/local/bin/
$ curl -Lo fission https://github.com/fission/fission/releases/download/nightly20170705/fission-cli-linux && chmod +x fission && sudo mv fission /usr/local/bin/
Windows:
For Windows, you can use the linux binary on WSL. Or you can download this windows executable: https://github.com/fission/fission/releases/download/nightly20170705/fission-cli-windows.exe
2. Set the FISSION_URL and FISSION_ROUTER environment variables.
FISSION_URL is used by the fission CLI to find the server.
@@ -0,0 +1,486 @@
{{ if .Values.openshift }}
# For openshift
---
apiVersion: v1
kind: ProjectRequest
metadata:
name: fission
labels:
name: fission
---
apiVersion: v1
kind: ProjectRequest
metadata:
name: {{ .Values.functionNamespace }}
labels:
name: fission-function
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: fission-admin
namespace: {{ .Release.Namespace }}
---
apiVersion: v1
kind: ClusterRole
metadata:
name: fission:fission-admin
rules:
- apiGroups:
- extensions
attributeRestrictions: null
resources:
- deployments
verbs:
- create
- get
- list
- update
- watch
- apiGroups:
- ""
attributeRestrictions: null
resources:
- pods
verbs:
- get
- list
- update
---
apiVersion: v1
groupNames: null
kind: RoleBinding
metadata:
name: fission:fission-admin
namespace: {{ .Values.functionNamespace }}
roleRef:
name: fission:fission-admin
subjects:
- kind: ServiceAccount
name: fission-admin
namespace: {{ .Release.Namespace }}
userNames:
- system:serviceaccount:fission:fission-admin
{{ else }}
# For all environments except openshift
---
apiVersion: v1
kind: Namespace
metadata:
name: {{ .Values.functionNamespace }}
labels:
name: fission-function
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: fission-svc
namespace: {{ .Release.Namespace }}
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: fission-admin
namespace: {{ .Release.Namespace }}
subjects:
- kind: ServiceAccount
name: fission-svc
namespace: {{ .Release.Namespace }}
roleRef:
kind: ClusterRole
name: admin
apiGroup: rbac.authorization.k8s.io
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: fission-function-admin
namespace: {{ .Values.functionNamespace }}
subjects:
- kind: ServiceAccount
name: fission-svc
namespace: {{ .Release.Namespace }}
roleRef:
kind: ClusterRole
name: admin
apiGroup: rbac.authorization.k8s.io
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: fission-tpr
subjects:
- kind: ServiceAccount
name: fission-svc
namespace: {{ .Release.Namespace }}
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: fission-fetcher
namespace: {{ .Values.functionNamespace }}
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: fission-fetcher-tpr
subjects:
- kind: ServiceAccount
name: fission-fetcher
namespace: {{ .Values.functionNamespace }}
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
{{ end }}
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: controller
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
replicas: 1
template:
metadata:
labels:
svc: controller
spec:
containers:
- name: controller
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
imagePullPolicy: {{ .Values.pullPolicy }}
command: ["/fission-bundle"]
args: ["--controllerPort", "8888"]
serviceAccount: fission-svc
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: router
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
replicas: 1
template:
metadata:
labels:
svc: router
spec:
containers:
- name: router
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
imagePullPolicy: {{ .Values.pullPolicy }}
command: ["/fission-bundle"]
args: ["--routerPort", "8888", "--poolmgrUrl", "http://poolmgr.{{ .Release.Namespace }}"]
serviceAccount: fission-svc
---
apiVersion: v1
kind: Service
metadata:
name: poolmgr
labels:
svc: poolmgr
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8888
selector:
svc: poolmgr
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: poolmgr
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
replicas: 1
template:
metadata:
labels:
svc: poolmgr
spec:
containers:
- name: poolmgr
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
imagePullPolicy: {{ .Values.pullPolicy }}
command: ["/fission-bundle"]
args: ["--poolmgrPort", "8888", "--namespace", "{{ .Values.functionNamespace }}", "--fission-namespace", "{{ .Release.Namespace }}"]
env:
- name: FETCHER_IMAGE
value: "{{ .Values.fetcherImage }}:{{ .Values.fetcherImageTag }}"
- name: FETCHER_IMAGE_PULL_POLICY
value: "{{ .Values.pullPolicy }}"
serviceAccount: fission-svc
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: kubewatcher
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
replicas: 1
template:
metadata:
labels:
svc: kubewatcher
spec:
containers:
- name: kubewatcher
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
imagePullPolicy: {{ .Values.pullPolicy }}
command: ["/fission-bundle"]
args: ["--kubewatcher"]
serviceAccount: fission-svc
---
apiVersion: v1
kind: Service
metadata:
name: influxdb
labels:
svc: influxdb
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
type: ClusterIP
ports:
- port: 8086
targetPort: 8086
selector:
svc: influxdb
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: influxdb
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
replicas: 1
template:
metadata:
labels:
svc: influxdb
spec:
containers:
- name: influxdb
image: tutum/influxdb
env:
- name: PRE_CREATE_DB
value: fissionFunctionLog
- name: ADMIN_USER
valueFrom:
secretKeyRef:
name: influxdb
key: username
- name: INFLUXDB_INIT_PWD
valueFrom:
secretKeyRef:
name: influxdb
key: password
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: logger
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
template:
metadata:
labels:
svc: logger
spec:
containers:
- name: logger
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
imagePullPolicy: {{ .Values.pullPolicy }}
command: ["/fission-bundle"]
args: ["--logger"]
volumeMounts:
- name: container-log
mountPath: /var/log/containers
readOnly: true
- name: docker-log
mountPath: /var/lib/docker/containers
readOnly: true
- name: fission-log
mountPath: /var/log/fission
readOnly: false
ports:
- containerPort: 1234
hostPort: 1234
protocol: TCP
- name: fluentd
image: {{ .Values.logger.fluentdImage }}
imagePullPolicy: {{ .Values.pullPolicy }}
env:
- name: INFLUXDB_ADDRESS
value: influxdb
- name: INFLUXDB_PORT
value: "8086"
- name: INFLUXDB_DBNAME
value: "fissionFunctionLog"
- name: INFLUXDB_USERNAME
valueFrom:
secretKeyRef:
name: influxdb
key: username
- name: INFLUXDB_PASSWD
valueFrom:
secretKeyRef:
name: influxdb
key: password
volumeMounts:
- name: container-log
mountPath: /var/log/containers
readOnly: true
- name: docker-log
mountPath: /var/lib/docker/containers
readOnly: true
- name: fission-log
mountPath: /var/log/fission
readOnly: false
serviceAccount: fission-svc
volumes:
- name: container-log
hostPath:
path: /var/log/containers
- name: docker-log
hostPath:
path: /var/lib/docker/containers
- name: fission-log
hostPath:
path: /var/log/fission
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: timer
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
replicas: 1
template:
metadata:
labels:
svc: timer
spec:
containers:
- name: timer
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
imagePullPolicy: {{ .Values.pullPolicy }}
command: ["/fission-bundle"]
args: ["--timer"]
serviceAccount: fission-svc
#
# This is commented out until fission-ui allows configuring the
# namespace. Right now it just crashes if Release.Namespace !=
# "fission".
#
#---
#apiVersion: extensions/v1beta1
#kind: Deployment
#metadata:
# name: fission-ui
# labels:
# chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
#spec:
# replicas: 1
# template:
# metadata:
# labels:
# svc: fission-ui
# spec:
# containers:
# - name: nginx
# image: {{ .Values.fissionUiImage }}
# - name: kubectl-proxy
# image: lachlanevenson/k8s-kubectl
# args: ["proxy", "--port", "8001", "--address", "127.0.0.1"]
# serviceAccount: fission-svc
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
svc: nats-streaming
name: nats-streaming
spec:
replicas: 1
template:
metadata:
labels:
svc: nats-streaming
spec:
containers:
- name: nats-streaming
image: nats-streaming
args: ["--cluster_id", "{{ .Values.nats.clusterID }}", "--auth", "{{ .Values.nats.authToken }}"]
ports:
- containerPort: 4222
hostPort: 4222
protocol: TCP
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: mqtrigger
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
replicas: 1
template:
metadata:
labels:
svc: mqtrigger
spec:
containers:
- name: mqtrigger
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
imagePullPolicy: {{ .Values.pullPolicy }}
command: ["/fission-bundle"]
args: ["--mqt"]
env:
- name: MESSAGE_QUEUE_TYPE
value: nats-streaming
- name: MESSAGE_QUEUE_URL
value: nats://{{ .Values.nats.authToken }}@nats-streaming:4222
serviceAccount: fission-svc
+10
View File
@@ -0,0 +1,10 @@
apiVersion: v1
kind: Secret
metadata:
name: influxdb
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
type: Opaque
data:
username: {{ .Values.logger.influxdbAdmin | b64enc | quote }}
password: {{ randAlphaNum 20 | b64enc | quote }}
+67
View File
@@ -0,0 +1,67 @@
apiVersion: v1
kind: Service
metadata:
name: router
labels:
svc: router
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
type: {{ .Values.serviceType }}
ports:
- port: 80
targetPort: 8888
nodePort: {{ .Values.routerPort }}
selector:
svc: router
---
apiVersion: v1
kind: Service
metadata:
name: controller
labels:
svc: controller
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
type: {{ .Values.serviceType }}
ports:
- port: 80
targetPort: 8888
nodePort: {{ .Values.controllerPort }}
selector:
svc: controller
---
apiVersion: v1
kind: Service
metadata:
name: fission-ui
labels:
svc: fission-ui
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
type: {{ .Values.serviceType }}
ports:
- port: 80
targetPort: 80
nodePort: 31319
selector:
svc: fission-ui
---
apiVersion: v1
kind: Service
metadata:
name: nats-streaming
labels:
svc: nats-streaming
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
type: {{ .Values.serviceType }}
ports:
- port: 4222
targetPort: 4222
nodePort: 31316
selector:
svc: nats-streaming
+50
View File
@@ -0,0 +1,50 @@
#
# Fission chart configuration
#
## Kubernetes configuration
## For minikube, set this to NodePort, elsewhere use LoadBalancer.
serviceType: LoadBalancer
## Fission image repository
image: fission/fission-bundle
## Image pull policy
pullPolicy: IfNotPresent
## Fission image version
imageTag: v0.2.0-20170822
## Fission fetcher repository
fetcherImage: fission/fetcher
## Fission fetcher image version
fetcherImageTag: v0.2.0-20170822
## Port at which Fission controller service should be exposed
controllerPort: 31313
## Port at which Fission router service should be exposed
routerPort: 31314
## Namespace in which to run fission functions (this is different from
## the release namespace)
functionNamespace: fission-function
## Set up openshift RBAC rule
openshift: false
## Logger config
logger:
influxdbAdmin: "admin"
fluentdImage: fission/fluentd
## Fission ui config
fissionUiImage: fission/fission-ui:0.1.0
## Message queue trigger config
### NATS Streaming
nats:
authToken: "defaultFissionAuthToken"
clusterID: "fissionMQTrigger"
+21
View File
@@ -0,0 +1,21 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
+16
View File
@@ -0,0 +1,16 @@
apiVersion: v1
name: fission-core
version: v0.2.0-20170822
description: Fission is a fast serverless framework for Kubernetes.
keywords:
- fission
- serverless
home: http://fission.io/
maintainers:
- name: Sanket Sudake
email: sanketsudake@gmail.com
- name: Soam Vasani
email: soamvasani@platform9.com
- name: Ta Ching Chen
email: contact@tachingchen.com
engine: gotpl
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
+40
View File
@@ -0,0 +1,40 @@
1. Install the client CLI.
Mac:
$ curl -Lo fission https://github.com/fission/fission/releases/download/nightly20170705/fission-cli-osx && chmod +x fission && sudo mv fission /usr/local/bin/
Linux:
$ curl -Lo fission https://github.com/fission/fission/releases/download/nightly20170705/fission-cli-linux && chmod +x fission && sudo mv fission /usr/local/bin/
Windows:
For Windows, you can use the linux binary on WSL. Or you can download this windows executable: https://github.com/fission/fission/releases/download/nightly20170705/fission-cli-windows.exe
2. Set the FISSION_URL and FISSION_ROUTER environment variables.
FISSION_URL is used by the fission CLI to find the server.
FISSION_URL should be prefixed with a http://. (FISSION_ROUTER is only needed for the examples below to work.)
{{- if contains "NodePort" .Values.serviceType }}
$ export FISSION_URL=http://$(minikube ip):{{ .Values.controllerPort }}
$ export FISSION_ROUTER=$(minikube ip):{{ .Values.routerPort }}
{{- else if contains "LoadBalancer" .Values.serviceType }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl --namespace fission get -w svc' and 'kubectl --namespace fission get svc -w router'
$ export FISSION_URL=http://$(kubectl --namespace fission get svc controller -o=jsonpath='{..ip}')
$ export FISSION_ROUTER=$(kubectl --namespace fission get svc router -o=jsonpath='{..ip}')
{{- end }}
3. Finally, you're ready to use Fission!
$ fission env create --name nodejs --image fission/node-env
$ curl https://raw.githubusercontent.com/fission/fission/master/examples/nodejs/hello.js > hello.js
$ fission function create --name hello --env nodejs --code hello.js
$ fission route create --method GET --url /hello --function hello
$ curl http://$FISSION_ROUTER/hello
Hello, world!
@@ -0,0 +1,16 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 24 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 24 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
*/}}
{{- define "fullname" -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- printf "%s-%s" .Release.Name $name | trunc 24 | trimSuffix "-" -}}
{{- end -}}
@@ -0,0 +1,287 @@
{{ if .Values.openshift }}
# For openshift
---
apiVersion: v1
kind: ProjectRequest
metadata:
name: fission
labels:
name: fission
---
apiVersion: v1
kind: ProjectRequest
metadata:
name: {{ .Values.functionNamespace }}
labels:
name: fission-function
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: fission-admin
namespace: {{ .Release.Namespace }}
---
apiVersion: v1
kind: ClusterRole
metadata:
name: fission:fission-admin
rules:
- apiGroups:
- extensions
attributeRestrictions: null
resources:
- deployments
verbs:
- create
- get
- list
- update
- watch
- apiGroups:
- ""
attributeRestrictions: null
resources:
- pods
verbs:
- get
- list
- update
---
apiVersion: v1
groupNames: null
kind: RoleBinding
metadata:
name: fission:fission-admin
namespace: {{ .Values.functionNamespace }}
roleRef:
name: fission:fission-admin
subjects:
- kind: ServiceAccount
name: fission-admin
namespace: {{ .Release.Namespace }}
userNames:
- system:serviceaccount:fission:fission-admin
{{ else }}
# For all environments except openshift
---
apiVersion: v1
kind: Namespace
metadata:
name: {{ .Values.functionNamespace }}
labels:
name: fission-function
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: fission-svc
namespace: {{ .Release.Namespace }}
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: fission-admin
namespace: {{ .Release.Namespace }}
subjects:
- kind: ServiceAccount
name: fission-svc
namespace: {{ .Release.Namespace }}
roleRef:
kind: ClusterRole
name: admin
apiGroup: rbac.authorization.k8s.io
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: fission-function-admin
namespace: {{ .Values.functionNamespace }}
subjects:
- kind: ServiceAccount
name: fission-svc
namespace: {{ .Release.Namespace }}
roleRef:
kind: ClusterRole
name: admin
apiGroup: rbac.authorization.k8s.io
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: fission-tpr
subjects:
- kind: ServiceAccount
name: fission-svc
namespace: {{ .Release.Namespace }}
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: fission-fetcher
namespace: {{ .Values.functionNamespace }}
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: fission-fetcher-tpr
subjects:
- kind: ServiceAccount
name: fission-fetcher
namespace: {{ .Values.functionNamespace }}
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
{{ end }}
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: controller
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
replicas: 1
template:
metadata:
labels:
svc: controller
spec:
containers:
- name: controller
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
imagePullPolicy: {{ .Values.pullPolicy }}
command: ["/fission-bundle"]
args: ["--controllerPort", "8888"]
serviceAccount: fission-svc
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: router
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
replicas: 1
template:
metadata:
labels:
svc: router
spec:
containers:
- name: router
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
imagePullPolicy: {{ .Values.pullPolicy }}
command: ["/fission-bundle"]
args: ["--routerPort", "8888", "--poolmgrUrl", "http://poolmgr.{{ .Release.Namespace }}"]
serviceAccount: fission-svc
---
apiVersion: v1
kind: Service
metadata:
name: poolmgr
labels:
svc: poolmgr
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8888
selector:
svc: poolmgr
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: poolmgr
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
replicas: 1
template:
metadata:
labels:
svc: poolmgr
spec:
containers:
- name: poolmgr
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
imagePullPolicy: {{ .Values.pullPolicy }}
command: ["/fission-bundle"]
args: ["--poolmgrPort", "8888", "--namespace", "{{ .Values.functionNamespace }}", "--fission-namespace", "{{ .Release.Namespace }}"]
env:
- name: FETCHER_IMAGE
value: "{{ .Values.fetcherImage }}:{{ .Values.fetcherImageTag }}"
- name: FETCHER_IMAGE_PULL_POLICY
value: "{{ .Values.pullPolicy }}"
serviceAccount: fission-svc
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: kubewatcher
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
replicas: 1
template:
metadata:
labels:
svc: kubewatcher
spec:
containers:
- name: kubewatcher
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
imagePullPolicy: {{ .Values.pullPolicy }}
command: ["/fission-bundle"]
args: ["--kubewatcher"]
serviceAccount: fission-svc
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: timer
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
replicas: 1
template:
metadata:
labels:
svc: timer
spec:
containers:
- name: timer
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
imagePullPolicy: {{ .Values.pullPolicy }}
command: ["/fission-bundle"]
args: ["--timer"]
serviceAccount: fission-svc
@@ -29,4 +29,4 @@ spec:
targetPort: 8888
nodePort: {{ .Values.controllerPort }}
selector:
svc: controller
svc: controller
@@ -6,11 +6,20 @@
## For minikube, set this to NodePort, elsewhere use LoadBalancer.
serviceType: LoadBalancer
## Fission image repostiroy
## Fission image repository
image: fission/fission-bundle
## Fission image version
imageTag: alpha20170124
imageTag: v0.2.0-20170822
## Image pull policy
pullPolicy: IfNotPresent
## Fission fetcher repository
fetcherImage: fission/fetcher
## Fission fetcher image version
fetcherImageTag: v0.2.0-20170822
## Port at which Fission controller service should be exposed
controllerPort: 31313
@@ -21,3 +30,6 @@ routerPort: 31314
## Namespace in which to run fission functions (this is different from
## the release namespace)
functionNamespace: fission-function
## Set up openshift RBAC rule
openshift: false
-12
View File
@@ -1,12 +0,0 @@
apiVersion: v1
name: fission
version: 0.1.0
description: A Helm chart for Fission. Fission is a fast serverless framework for Kubernetes.
keywords:
- fission
- serverless
home: http://fission.io/
maintainers:
- name: Sanket Sudake
email: sanketsudake@gmail.com
engine: gotpl
-58
View File
@@ -1,58 +0,0 @@
# Fission
[Fission](http://fission.io/) is a framework for serverless functions on Kubernetes.
## Prerequisites
- Kubernetes 1.4+ with Beta APIs enabled
## Installing the chart
To install the chart with the release name `my-release`,
```bash
$ helm install --name my-release ./fission
```
## Uninstalling the chart
To uninstall/delete chart,
```bash
$ helm delete my-release
```
## Configuration
The following table lists the configurable parameters of the Fission chart and their default values.
| Parameter | Description | Default |
| ------------------- | ------------------------------------------ | ------------------------ |
| `serviceType` | Type of service to use | `LoadBalancer`. |
| `image` | Fission image | `fission/fission-bundle` |
| `imageTag` | Fission image tag | `alpha20170124` |
| `controllerPort` | Fission Controller Service Port | `31313` |
| `routerPort` | Fission Router Service Port | `31314` |
| `functionNamespace` | Namespace for Fission functions | `fission-function` |
Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example,
```bash
$ helm install --name my-release --set image=custom/fission-bundle,imageTag=v1 fission
```
If you're using minikube, set serviceType to NodePort:
```bash
$ helm install --name my-release --set serviceType=NodePort fission
```
You can also set parameters with a yaml file (see values.yaml for
what it should look like):
```bash
$ helm install --name my-release -f values.yaml fission
```
-141
View File
@@ -1,141 +0,0 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: {{ .Values.functionNamespace }}
labels:
name: fission-function
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: controller
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
replicas: 1
template:
metadata:
labels:
svc: controller
spec:
containers:
- name: controller
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
command: ["/fission-bundle"]
args: ["--controllerPort", "8888", "--filepath", "/filestore"]
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: router
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
replicas: 1
template:
metadata:
labels:
svc: router
spec:
containers:
- name: router
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
command: ["/fission-bundle"]
args: ["--routerPort", "8888"]
---
apiVersion: v1
kind: Service
metadata:
name: poolmgr
labels:
svc: poolmgr
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
ports:
- port: 80
targetPort: 8888
selector:
svc: poolmgr
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: poolmgr
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
replicas: 1
template:
metadata:
labels:
svc: poolmgr
spec:
containers:
- name: poolmgr
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
command: ["/fission-bundle"]
args: ["--poolmgrPort", "8888", "--namespace", "{{ .Values.functionNamespace }}"]
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: kubewatcher
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
replicas: 1
template:
metadata:
labels:
svc: kubewatcher
spec:
containers:
- name: kubewatcher
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
command: ["/fission-bundle"]
args: ["--kubewatcher"]
---
apiVersion: v1
kind: Service
metadata:
name: etcd
labels:
svc: etcd
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
ports:
- port: 2379
targetPort: 2379
selector:
svc: etcd
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: etcd
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
replicas: 1
template:
metadata:
labels:
svc: etcd
spec:
containers:
- name: etcd
image: quay.io/coreos/etcd
env:
- name: ETCD_LISTEN_CLIENT_URLS
value: http://0.0.0.0:2379
- name: ETCD_ADVERTISE_CLIENT_URLS
value: http://etcd:2379
+2 -6
View File
@@ -20,11 +20,7 @@ import (
"fmt"
)
func UrlForFunction(m *Metadata) string {
func UrlForFunction(name string) string {
prefix := "/fission-function"
if len(m.Uid) > 0 {
return fmt.Sprintf("%v/%v/%v", prefix, m.Name, m.Uid)
} else {
return fmt.Sprintf("%v/%v", prefix, m.Name)
}
return fmt.Sprintf("%v/%v", prefix, name)
}
+67 -38
View File
@@ -26,18 +26,17 @@ import (
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
kerrors "k8s.io/client-go/1.5/pkg/api/errors"
"github.com/fission/fission"
"github.com/fission/fission/fission/logdb"
"github.com/fission/fission/tpr"
)
type (
API struct {
FunctionStore
HTTPTriggerStore
TimeTriggerStore
EnvironmentStore
WatchStore
fissionClient *tpr.FissionClient
storageServiceUrl string
}
logDBConfig struct {
@@ -47,15 +46,17 @@ type (
}
)
func MakeAPI(rs *ResourceStore) *API {
api := &API{
FunctionStore: FunctionStore{ResourceStore: *rs},
HTTPTriggerStore: HTTPTriggerStore{ResourceStore: *rs},
TimeTriggerStore: TimeTriggerStore{ResourceStore: *rs},
EnvironmentStore: EnvironmentStore{ResourceStore: *rs},
WatchStore: WatchStore{ResourceStore: *rs},
func MakeAPI() (*API, error) {
api, err := makeTPRBackedAPI()
u := os.Getenv("STORAGE_SERVICE_URL")
if len(u) > 0 {
api.storageServiceUrl = strings.TrimSuffix(u, "/")
} else {
api.storageServiceUrl = "http://storagesvc"
}
return api
return api, err
}
func (api *API) respondWithSuccess(w http.ResponseWriter, resp []byte) {
@@ -69,6 +70,14 @@ func (api *API) respondWithSuccess(w http.ResponseWriter, resp []byte) {
func (api *API) respondWithError(w http.ResponseWriter, err error) {
debug.PrintStack()
// this error type comes with an HTTP code, so just use that
se, ok := err.(*kerrors.StatusError)
if ok {
http.Error(w, string(se.ErrStatus.Reason), int(se.ErrStatus.Code))
return
}
code, msg := fission.GetHTTPError(err)
log.Errorf("Error: %v: %v", code, msg)
http.Error(w, msg, code)
@@ -96,41 +105,61 @@ func (api *API) HomeHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "{\"message\": \"Fission API\", \"version\": \"0.1.0\"}\n")
}
func (api *API) ApiVersionMismatchHandler(w http.ResponseWriter, r *http.Request) {
err := fission.MakeError(fission.ErrorNotFound, "Fission server supports API v2 only - v1 is not supported. Please upgrade your Fission client / CLI")
api.respondWithError(w, err)
}
func (api *API) Serve(port int) {
r := mux.NewRouter()
// Give a useful error message if an older CLI attempts to make a request
r.HandleFunc(`/v1/{rest:[a-zA-Z0-9=\-\/]+}`, api.ApiVersionMismatchHandler)
r.HandleFunc("/", api.HomeHandler)
r.HandleFunc("/v1/functions", api.FunctionApiList).Methods("GET")
r.HandleFunc("/v1/functions", api.FunctionApiCreate).Methods("POST")
r.HandleFunc("/v1/functions/{function}", api.FunctionApiGet).Methods("GET")
r.HandleFunc("/v1/functions/{function}", api.FunctionApiUpdate).Methods("PUT")
r.HandleFunc("/v1/functions/{function}", api.FunctionApiDelete).Methods("DELETE")
r.HandleFunc("/v2/packages", api.PackageApiList).Methods("GET")
r.HandleFunc("/v2/packages", api.PackageApiCreate).Methods("POST")
r.HandleFunc("/v2/packages/{package}", api.PackageApiGet).Methods("GET")
r.HandleFunc("/v2/packages/{package}", api.PackageApiUpdate).Methods("PUT")
r.HandleFunc("/v2/packages/{package}", api.PackageApiDelete).Methods("DELETE")
r.HandleFunc("/v1/triggers/http", api.HTTPTriggerApiList).Methods("GET")
r.HandleFunc("/v1/triggers/http", api.HTTPTriggerApiCreate).Methods("POST")
r.HandleFunc("/v1/triggers/http/{httpTrigger}", api.HTTPTriggerApiGet).Methods("GET")
r.HandleFunc("/v1/triggers/http/{httpTrigger}", api.HTTPTriggerApiUpdate).Methods("PUT")
r.HandleFunc("/v1/triggers/http/{httpTrigger}", api.HTTPTriggerApiDelete).Methods("DELETE")
r.HandleFunc("/v2/functions", api.FunctionApiList).Methods("GET")
r.HandleFunc("/v2/functions", api.FunctionApiCreate).Methods("POST")
r.HandleFunc("/v2/functions/{function}", api.FunctionApiGet).Methods("GET")
r.HandleFunc("/v2/functions/{function}", api.FunctionApiUpdate).Methods("PUT")
r.HandleFunc("/v2/functions/{function}", api.FunctionApiDelete).Methods("DELETE")
r.HandleFunc("/v1/environments", api.EnvironmentApiList).Methods("GET")
r.HandleFunc("/v1/environments", api.EnvironmentApiCreate).Methods("POST")
r.HandleFunc("/v1/environments/{environment}", api.EnvironmentApiGet).Methods("GET")
r.HandleFunc("/v1/environments/{environment}", api.EnvironmentApiUpdate).Methods("PUT")
r.HandleFunc("/v1/environments/{environment}", api.EnvironmentApiDelete).Methods("DELETE")
r.HandleFunc("/v2/triggers/http", api.HTTPTriggerApiList).Methods("GET")
r.HandleFunc("/v2/triggers/http", api.HTTPTriggerApiCreate).Methods("POST")
r.HandleFunc("/v2/triggers/http/{httpTrigger}", api.HTTPTriggerApiGet).Methods("GET")
r.HandleFunc("/v2/triggers/http/{httpTrigger}", api.HTTPTriggerApiUpdate).Methods("PUT")
r.HandleFunc("/v2/triggers/http/{httpTrigger}", api.HTTPTriggerApiDelete).Methods("DELETE")
r.HandleFunc("/v1/watches", api.WatchApiList).Methods("GET")
r.HandleFunc("/v1/watches", api.WatchApiCreate).Methods("POST")
r.HandleFunc("/v1/watches/{watch}", api.WatchApiGet).Methods("GET")
r.HandleFunc("/v1/watches/{watch}", api.WatchApiUpdate).Methods("PUT")
r.HandleFunc("/v1/watches/{watch}", api.WatchApiDelete).Methods("DELETE")
r.HandleFunc("/v2/environments", api.EnvironmentApiList).Methods("GET")
r.HandleFunc("/v2/environments", api.EnvironmentApiCreate).Methods("POST")
r.HandleFunc("/v2/environments/{environment}", api.EnvironmentApiGet).Methods("GET")
r.HandleFunc("/v2/environments/{environment}", api.EnvironmentApiUpdate).Methods("PUT")
r.HandleFunc("/v2/environments/{environment}", api.EnvironmentApiDelete).Methods("DELETE")
r.HandleFunc("/v1/triggers/time", api.TimeTriggerApiList).Methods("GET")
r.HandleFunc("/v1/triggers/time", api.TimeTriggerApiCreate).Methods("POST")
r.HandleFunc("/v1/triggers/time/{timeTrigger}", api.TimeTriggerApiGet).Methods("GET")
r.HandleFunc("/v1/triggers/time/{timeTrigger}", api.TimeTriggerApiUpdate).Methods("PUT")
r.HandleFunc("/v1/triggers/time/{timeTrigger}", api.TimeTriggerApiDelete).Methods("DELETE")
r.HandleFunc("/v2/watches", api.WatchApiList).Methods("GET")
r.HandleFunc("/v2/watches", api.WatchApiCreate).Methods("POST")
r.HandleFunc("/v2/watches/{watch}", api.WatchApiGet).Methods("GET")
r.HandleFunc("/v2/watches/{watch}", api.WatchApiUpdate).Methods("PUT")
r.HandleFunc("/v2/watches/{watch}", api.WatchApiDelete).Methods("DELETE")
r.HandleFunc("/v2/triggers/time", api.TimeTriggerApiList).Methods("GET")
r.HandleFunc("/v2/triggers/time", api.TimeTriggerApiCreate).Methods("POST")
r.HandleFunc("/v2/triggers/time/{timeTrigger}", api.TimeTriggerApiGet).Methods("GET")
r.HandleFunc("/v2/triggers/time/{timeTrigger}", api.TimeTriggerApiUpdate).Methods("PUT")
r.HandleFunc("/v2/triggers/time/{timeTrigger}", api.TimeTriggerApiDelete).Methods("DELETE")
r.HandleFunc("/v2/triggers/messagequeue", api.MessageQueueTriggerApiList).Methods("GET")
r.HandleFunc("/v2/triggers/messagequeue", api.MessageQueueTriggerApiCreate).Methods("POST")
r.HandleFunc("/v2/triggers/messagequeue/{mqTrigger}", api.MessageQueueTriggerApiGet).Methods("GET")
r.HandleFunc("/v2/triggers/messagequeue/{mqTrigger}", api.MessageQueueTriggerApiUpdate).Methods("PUT")
r.HandleFunc("/v2/triggers/messagequeue/{mqTrigger}", api.MessageQueueTriggerApiDelete).Methods("DELETE")
r.HandleFunc("/proxy/{dbType}", api.FunctionLogsApiPost).Methods("POST")
r.HandleFunc("/proxy/storage/v1/archive", api.StorageServiceProxy)
address := fmt.Sprintf(":%v", port)
+137 -215
View File
@@ -19,35 +19,49 @@ package controller
import (
"flag"
"io/ioutil"
"log"
"net/http"
"os"
"testing"
"time"
etcdClient "github.com/coreos/etcd/client"
log "github.com/sirupsen/logrus"
"golang.org/x/net/context"
"k8s.io/client-go/1.5/pkg/api"
"github.com/fission/fission"
"github.com/fission/fission/controller/client"
"github.com/fission/fission/tpr"
)
var g struct {
client *client.Client
}
func assertNameReuseFails(err error, name string) {
func panicIf(err error) {
if err != nil {
log.Panicf("err: %v", err)
}
}
func assert(c bool, msg string) {
if !c {
log.Fatalf("assert failed: %v", msg)
}
}
func assertNameReuseFailure(err error, name string) {
assert(err != nil, "recreating "+name+" with same name must fail")
fe, ok := err.(fission.Error)
assert(ok, "error must be a fission Error")
assert(fe.Code == fission.ErrorNameExists, "error must be a name exists error")
}
func assertNotFoundFails(err error, name string) {
func assertNotFoundFailure(err error, name string) {
assert(err != nil, "requesting a non-existent "+name+" must fail")
fe, ok := err.(fission.Error)
assert(ok, "error must be a fission Error")
assert(fe.Code == fission.ErrorNotFound, "error must be a not found error")
if fe.Code != fission.ErrorNotFound {
log.Fatalf("error must be a not found error: %v", fe)
}
}
func assertCronSpecFails(err error) {
@@ -58,68 +72,39 @@ func assertCronSpecFails(err error) {
}
func TestFunctionApi(t *testing.T) {
log.SetFormatter(&log.TextFormatter{DisableColors: true})
name1 := "foo"
name2 := "bar"
testFunc := &fission.Function{
Metadata: fission.Metadata{
Name: "foo",
Uid: "",
testFunc := &tpr.Function{
Metadata: api.ObjectMeta{
Name: name1,
Namespace: api.NamespaceDefault,
},
Environment: fission.Metadata{
Name: "nodejs",
Uid: "xxx",
Spec: fission.FunctionSpec{
EnvironmentName: "nodejs",
Deployment: fission.FunctionPackageRef{
FunctionName: "xxx",
},
},
Code: "code1",
}
_, err := g.client.FunctionGet(&fission.Metadata{Name: "foo"})
assertNotFoundFails(err, "function")
m, err := g.client.FunctionCreate(testFunc)
panicIf(err)
uid1 := m.Uid
//log.Printf("Created function %v: %v", m.Name, m.Uid)
_, err := g.client.FunctionGet(&api.ObjectMeta{
Name: testFunc.Metadata.Name,
Namespace: api.NamespaceDefault,
})
assertNotFoundFailure(err, "function")
_, err = g.client.FunctionCreate(testFunc)
assertNameReuseFails(err, "function")
code, err := g.client.FunctionGetRaw(m)
panicIf(err)
assert(string(code) == testFunc.Code, "code from FunctionGetRaw must match created function")
testFunc.Code = "code2"
m, err = g.client.FunctionUpdate(testFunc)
panicIf(err)
uid2 := m.Uid
//log.Printf("Updated function %v: %v", m.Name, m.Uid)
m.Uid = uid1
testFunc.Code = "code1"
f, err := g.client.FunctionGet(m)
panicIf(err)
testFunc.Metadata.Uid = m.Uid
//log.Printf("f = %#v", f)
//log.Printf("testFunc = %#v", testFunc)
assert(*f == *testFunc, "first version should match when read by uid")
_, err = g.client.FunctionCreate(testFunc)
assertNameReuseFailure(err, "function")
m.Uid = uid2
testFunc.Metadata.Uid = m.Uid
testFunc.Code = "code2"
f, err = g.client.FunctionGet(m)
testFunc.Spec.Deployment.FunctionName = "yyy"
_, err = g.client.FunctionUpdate(testFunc)
panicIf(err)
assert(*f == *testFunc, "second version should match when read by uid")
m.Uid = ""
testFunc.Metadata.Uid = uid2
testFunc.Code = "code2"
f, err = g.client.FunctionGet(m)
panicIf(err)
assert(*f == *testFunc, "second version should match when read as latest")
testFunc.Metadata.Name = "bar"
m, err = g.client.FunctionCreate(testFunc)
testFunc.Metadata.Name = name2
_, err = g.client.FunctionCreate(testFunc)
panicIf(err)
funcs, err := g.client.FunctionList()
@@ -127,7 +112,7 @@ func TestFunctionApi(t *testing.T) {
assert(len(funcs) == 2,
"created two functions, but didn't find them")
funcs_url := g.client.Url + "/v1/functions"
funcs_url := g.client.Url + "/v2/functions"
resp, err := http.Get(funcs_url)
panicIf(err)
defer resp.Body.Close()
@@ -141,112 +126,55 @@ func TestFunctionApi(t *testing.T) {
}
assert(found, "incorrect response content type")
err = g.client.FunctionDelete(&fission.Metadata{Name: "foo"})
err = g.client.FunctionDelete(&api.ObjectMeta{Name: name1, Namespace: api.NamespaceDefault})
panicIf(err)
err = g.client.FunctionDelete(&fission.Metadata{Name: "bar"})
err = g.client.FunctionDelete(&api.ObjectMeta{Name: name2, Namespace: api.NamespaceDefault})
panicIf(err)
}
func TestFunctionVersionApi(t *testing.T) {
testFunc := &fission.Function{
Metadata: fission.Metadata{
Name: "foo",
Uid: "",
},
Environment: fission.Metadata{
Name: "nodejs",
Uid: "xxx",
},
Code: "code1",
}
testFunc.Code = "code1"
m, err := g.client.FunctionCreate(testFunc)
panicIf(err)
uid1 := m.Uid
testFunc.Code = "code2"
m, err = g.client.FunctionUpdate(testFunc)
panicIf(err)
uid2 := m.Uid
err = g.client.FunctionDelete(&fission.Metadata{Name: "foo", Uid: uid1})
panicIf(err)
f, err := g.client.FunctionGet(&fission.Metadata{Name: "foo"})
panicIf(err)
assert(f.Metadata.Uid == uid2, "deleted version1, but version2 does not exist")
testFunc.Code = "code3"
m, err = g.client.FunctionUpdate(testFunc)
panicIf(err)
uid3 := m.Uid
err = g.client.FunctionDelete(&fission.Metadata{Name: "foo", Uid: uid3})
panicIf(err)
f, err = g.client.FunctionGet(&fission.Metadata{Name: "foo"})
panicIf(err)
assert(f.Metadata.Uid == uid2, "deleted version3, but version2 does not exist")
testFunc.Code = "code4"
m, err = g.client.FunctionUpdate(testFunc)
panicIf(err)
err = g.client.FunctionDelete(&fission.Metadata{Name: "foo"})
panicIf(err)
funcs, err := g.client.FunctionList()
panicIf(err)
assert(len(funcs) == 0,
"created one function with two versions(2 and 4), delete without uid but cannot delete them all")
}
func TestHTTPTriggerApi(t *testing.T) {
testTrigger := &fission.HTTPTrigger{
Metadata: fission.Metadata{
Name: "xxx",
Uid: "yyy",
testTrigger := &tpr.Httptrigger{
Metadata: api.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
UrlPattern: "/hello",
Function: fission.Metadata{
Name: "foo",
Uid: "",
Spec: fission.HTTPTriggerSpec{
RelativeURL: "/hello",
FunctionReference: fission.FunctionReference{
Type: fission.FunctionReferenceTypeFunctionName,
Name: "foo",
},
},
}
_, err := g.client.HTTPTriggerGet(&fission.Metadata{Name: "foo"})
assertNotFoundFails(err, "trigger")
_, err := g.client.HTTPTriggerGet(&api.ObjectMeta{
Name: testTrigger.Metadata.Name,
Namespace: api.NamespaceDefault,
})
assertNotFoundFailure(err, "httptrigger")
m, err := g.client.HTTPTriggerCreate(testTrigger)
panicIf(err)
defer g.client.HTTPTriggerDelete(m)
_, err = g.client.HTTPTriggerCreate(testTrigger)
assertNameReuseFails(err, "trigger")
assertNameReuseFailure(err, "httptrigger")
tr, err := g.client.HTTPTriggerGet(m)
panicIf(err)
testTrigger.Metadata.Uid = m.Uid
assert(*testTrigger == *tr, "trigger should match after reading")
assert(testTrigger.Spec == tr.Spec, "trigger should match after reading")
testTrigger.UrlPattern = "/hi"
m2, err := g.client.HTTPTriggerUpdate(testTrigger)
testTrigger.Spec.RelativeURL = "/hi"
_, err = g.client.HTTPTriggerUpdate(testTrigger)
panicIf(err)
m.Uid = m2.Uid
tr, err = g.client.HTTPTriggerGet(m)
panicIf(err)
testTrigger.Metadata.Uid = m.Uid
assert(*testTrigger == *tr, "trigger should match after reading")
testTrigger.Metadata.Name = "yyy"
m, err = g.client.HTTPTriggerCreate(testTrigger)
_, err = g.client.HTTPTriggerCreate(testTrigger)
assert(err != nil, "duplicate trigger should not be allowed")
testTrigger.UrlPattern = "/hi2"
m, err = g.client.HTTPTriggerCreate(testTrigger)
testTrigger.Spec.RelativeURL = "/hi2"
m2, err := g.client.HTTPTriggerCreate(testTrigger)
panicIf(err)
defer g.client.HTTPTriggerDelete(m)
defer g.client.HTTPTriggerDelete(m2)
ts, err := g.client.HTTPTriggerList()
panicIf(err)
@@ -254,42 +182,42 @@ func TestHTTPTriggerApi(t *testing.T) {
}
func TestEnvironmentApi(t *testing.T) {
testEnv := &fission.Environment{
Metadata: fission.Metadata{
Name: "xxx",
Uid: "yyy",
testEnv := &tpr.Environment{
Metadata: api.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: fission.EnvironmentSpec{
Runtime: fission.Runtime{
Image: "gcr.io/xyz",
},
},
RunContainerImageUrl: "gcr.io/xyz",
}
_, err := g.client.EnvironmentGet(&fission.Metadata{Name: "foo"})
assertNotFoundFails(err, "environment")
_, err := g.client.EnvironmentGet(&api.ObjectMeta{
Name: testEnv.Metadata.Name,
Namespace: api.NamespaceDefault,
})
assertNotFoundFailure(err, "environment")
m, err := g.client.EnvironmentCreate(testEnv)
panicIf(err)
defer g.client.EnvironmentDelete(m)
_, err = g.client.EnvironmentCreate(testEnv)
assertNameReuseFails(err, "environment")
assertNameReuseFailure(err, "environment")
tr, err := g.client.EnvironmentGet(m)
e, err := g.client.EnvironmentGet(m)
panicIf(err)
testEnv.Metadata.Uid = m.Uid
assert(*testEnv == *tr, "env should match after reading")
assert(testEnv.Spec == e.Spec, "env should match after reading")
testEnv.RunContainerImageUrl = "/hi"
m2, err := g.client.EnvironmentUpdate(testEnv)
testEnv.Spec.Runtime.Image = "another-img"
_, err = g.client.EnvironmentUpdate(testEnv)
panicIf(err)
m.Uid = m2.Uid
tr, err = g.client.EnvironmentGet(m)
testEnv.Metadata.Name = "bar"
m2, err := g.client.EnvironmentCreate(testEnv)
panicIf(err)
testEnv.Metadata.Uid = m.Uid
assert(*testEnv == *tr, "env should match after reading")
testEnv.Metadata.Name = "yyy"
m, err = g.client.EnvironmentCreate(testEnv)
panicIf(err)
defer g.client.EnvironmentDelete(m)
defer g.client.EnvironmentDelete(m2)
ts, err := g.client.EnvironmentList()
panicIf(err)
@@ -297,36 +225,38 @@ func TestEnvironmentApi(t *testing.T) {
}
func TestWatchApi(t *testing.T) {
testWatch := &fission.Watch{
Metadata: fission.Metadata{
Name: "xxx",
Uid: "yyy",
testWatch := &tpr.Kuberneteswatchtrigger{
Metadata: api.ObjectMeta{
Name: "xxx",
Namespace: api.NamespaceDefault,
},
Namespace: "default",
ObjType: "pod",
LabelSelector: "",
FieldSelector: "",
Function: fission.Metadata{
Name: "foo",
Uid: "",
Spec: fission.KubernetesWatchTriggerSpec{
Namespace: "default",
Type: "pod",
FunctionReference: fission.FunctionReference{
Type: fission.FunctionReferenceTypeFunctionName,
Name: "foo",
},
},
Target: "",
}
_, err := g.client.WatchGet(&fission.Metadata{Name: "foo"})
assertNotFoundFails(err, "watch")
_, err := g.client.WatchGet(&api.ObjectMeta{
Name: testWatch.Metadata.Name,
Namespace: api.NamespaceDefault,
})
assertNotFoundFailure(err, "watch")
m, err := g.client.WatchCreate(testWatch)
panicIf(err)
defer g.client.WatchDelete(m)
_, err = g.client.WatchCreate(testWatch)
assertNameReuseFails(err, "watch")
assertNameReuseFailure(err, "watch")
w, err := g.client.WatchGet(m)
panicIf(err)
testWatch.Metadata.Uid = m.Uid
w.Target = ""
assert(*testWatch == *w, "watch should match after reading")
assert((testWatch.Spec.Namespace == w.Spec.Namespace &&
testWatch.Spec.Type == w.Spec.Type &&
testWatch.Spec.FunctionReference == w.Spec.FunctionReference), "watch should match after reading")
testWatch.Metadata.Name = "yyy"
m2, err := g.client.WatchCreate(testWatch)
@@ -339,45 +269,40 @@ func TestWatchApi(t *testing.T) {
}
func TestTimeTriggerApi(t *testing.T) {
testTrigger := &fission.TimeTrigger{
Metadata: fission.Metadata{
Name: "xxx",
Uid: "yyy",
testTrigger := &tpr.Timetrigger{
Metadata: api.ObjectMeta{
Name: "xxx",
Namespace: api.NamespaceDefault,
},
Cron: "0 30 * * * *",
Function: fission.Metadata{
Name: "foo",
Uid: "",
Spec: fission.TimeTriggerSpec{
Cron: "0 30 * * * *",
FunctionReference: fission.FunctionReference{
Type: fission.FunctionReferenceTypeFunctionName,
Name: "asdf",
},
},
}
_, err := g.client.TimeTriggerGet(&fission.Metadata{Name: "foo"})
assertNotFoundFails(err, "trigger")
_, err := g.client.TimeTriggerGet(&api.ObjectMeta{Name: testTrigger.Metadata.Name})
assertNotFoundFailure(err, "trigger")
m, err := g.client.TimeTriggerCreate(testTrigger)
panicIf(err)
defer g.client.TimeTriggerDelete(m)
_, err = g.client.TimeTriggerCreate(testTrigger)
assertNameReuseFails(err, "trigger")
assertNameReuseFailure(err, "trigger")
tr, err := g.client.TimeTriggerGet(m)
panicIf(err)
testTrigger.Metadata.Uid = m.Uid
assert(*testTrigger == *tr, "trigger should match after reading")
assert(testTrigger.Spec == tr.Spec, "trigger should match after reading")
testTrigger.Cron = "@hourly"
m2, err := g.client.TimeTriggerUpdate(testTrigger)
testTrigger.Spec.Cron = "@hourly"
_, err = g.client.TimeTriggerUpdate(testTrigger)
panicIf(err)
m.Uid = m2.Uid
tr, err = g.client.TimeTriggerGet(m)
panicIf(err)
testTrigger.Metadata.Uid = m.Uid
assert(*testTrigger == *tr, "trigger should match after reading")
testTrigger.Metadata.Name = "yyy"
testTrigger.Cron = "Not valid cron spec"
m, err = g.client.TimeTriggerCreate(testTrigger)
testTrigger.Spec.Cron = "Not valid cron spec"
_, err = g.client.TimeTriggerCreate(testTrigger)
assertCronSpecFails(err)
ts, err := g.client.TimeTriggerList()
@@ -388,21 +313,18 @@ func TestTimeTriggerApi(t *testing.T) {
func TestMain(m *testing.M) {
flag.Parse()
fileStore, ks, rs := getTestResourceStore()
defer os.RemoveAll(fileStore.root)
// skip test if no cluster available for testing
kubeconfig := os.Getenv("KUBECONFIG")
if len(kubeconfig) == 0 {
log.Println("Skipping test, no kubernetes cluster")
return
}
api := MakeAPI(rs)
go Start(8888)
time.Sleep(time.Second)
g.client = client.MakeClient("http://localhost:8888")
ks.Delete(context.Background(), "Function", &etcdClient.DeleteOptions{Recursive: true})
ks.Delete(context.Background(), "HTTPTrigger", &etcdClient.DeleteOptions{Recursive: true})
ks.Delete(context.Background(), "TimeTrigger", &etcdClient.DeleteOptions{Recursive: true})
ks.Delete(context.Background(), "Environment", &etcdClient.DeleteOptions{Recursive: true})
ks.Delete(context.Background(), "Watch", &etcdClient.DeleteOptions{Recursive: true})
go api.Serve(8888)
time.Sleep(500 * time.Millisecond)
resp, err := http.Get("http://localhost:8888/")
panicIf(err)
assert(resp.StatusCode == 200, "http get status code on root")
+1 -553
View File
@@ -18,10 +18,7 @@ package client
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"strings"
@@ -73,7 +70,7 @@ func (c *Client) put(relativeUrl string, contentType string, body []byte) (*http
}
func (c *Client) url(relativeUrl string) string {
return c.Url + "/v1/" + relativeUrl
return c.Url + "/v2/" + relativeUrl
}
func (c *Client) handleResponse(resp *http.Response) ([]byte, error) {
@@ -91,552 +88,3 @@ func (c *Client) handleCreateResponse(resp *http.Response) ([]byte, error) {
body, err := ioutil.ReadAll(resp.Body)
return body, err
}
func (c *Client) FunctionCreate(f *fission.Function) (*fission.Metadata, error) {
orig := f.Code
f.Code = base64.StdEncoding.EncodeToString([]byte(f.Code))
defer func() { f.Code = orig }()
reqbody, err := json.Marshal(f)
if err != nil {
return nil, err
}
resp, err := http.Post(c.url("functions"), "application/json", bytes.NewReader(reqbody))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleCreateResponse(resp)
if err != nil {
return nil, err
}
var m fission.Metadata
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) FunctionGet(m *fission.Metadata) (*fission.Function, error) {
relativeUrl := fmt.Sprintf("functions/%v", m.Name)
if len(m.Uid) > 0 {
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
}
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var f fission.Function
err = json.Unmarshal(body, &f)
if err != nil {
return nil, err
}
dec, err := base64.StdEncoding.DecodeString(f.Code)
if err != nil {
return nil, err
}
f.Code = string(dec)
return &f, nil
}
func (c *Client) FunctionGetRaw(m *fission.Metadata) ([]byte, error) {
relativeUrl := fmt.Sprintf("functions/%v?raw=1", m.Name)
if len(m.Uid) > 0 {
relativeUrl += fmt.Sprintf("&uid=%v", m.Uid)
}
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
defer resp.Body.Close()
return c.handleResponse(resp)
}
func (c *Client) FunctionUpdate(f *fission.Function) (*fission.Metadata, error) {
orig := f.Code
f.Code = base64.StdEncoding.EncodeToString([]byte(f.Code))
defer func() { f.Code = orig }()
reqbody, err := json.Marshal(f)
if err != nil {
return nil, err
}
relativeUrl := fmt.Sprintf("functions/%v", f.Metadata.Name)
resp, err := c.put(relativeUrl, "application/json", reqbody)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var m fission.Metadata
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) FunctionDelete(m *fission.Metadata) error {
relativeUrl := fmt.Sprintf("functions/%v", m.Name)
if len(m.Uid) > 0 {
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
}
err := c.delete(relativeUrl)
return err
}
func (c *Client) FunctionList() ([]fission.Function, error) {
resp, err := http.Get(c.url("functions"))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
funcs := make([]fission.Function, 0)
err = json.Unmarshal(body, &funcs)
if err != nil {
return nil, err
}
return funcs, nil
}
func (c *Client) HTTPTriggerCreate(t *fission.HTTPTrigger) (*fission.Metadata, error) {
reqbody, err := json.Marshal(t)
if err != nil {
return nil, err
}
resp, err := http.Post(c.url("triggers/http"), "application/json", bytes.NewReader(reqbody))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleCreateResponse(resp)
if err != nil {
return nil, err
}
var m fission.Metadata
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) HTTPTriggerGet(m *fission.Metadata) (*fission.HTTPTrigger, error) {
relativeUrl := fmt.Sprintf("triggers/http/%v", m.Name)
if len(m.Uid) > 0 {
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
}
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var t fission.HTTPTrigger
err = json.Unmarshal(body, &t)
if err != nil {
return nil, err
}
return &t, nil
}
func (c *Client) HTTPTriggerUpdate(t *fission.HTTPTrigger) (*fission.Metadata, error) {
reqbody, err := json.Marshal(t)
if err != nil {
return nil, err
}
relativeUrl := fmt.Sprintf("triggers/http/%v", t.Metadata.Name)
resp, err := c.put(relativeUrl, "application/json", reqbody)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var m fission.Metadata
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) HTTPTriggerDelete(m *fission.Metadata) error {
relativeUrl := fmt.Sprintf("triggers/http/%v", m.Name)
if len(m.Uid) > 0 {
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
}
err := c.delete(relativeUrl)
return err
}
func (c *Client) HTTPTriggerList() ([]fission.HTTPTrigger, error) {
resp, err := http.Get(c.url("triggers/http"))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
triggers := make([]fission.HTTPTrigger, 0)
err = json.Unmarshal(body, &triggers)
if err != nil {
return nil, err
}
return triggers, nil
}
func (c *Client) EnvironmentCreate(env *fission.Environment) (*fission.Metadata, error) {
reqbody, err := json.Marshal(env)
if err != nil {
return nil, err
}
resp, err := http.Post(c.url("environments"), "application/json", bytes.NewReader(reqbody))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleCreateResponse(resp)
if err != nil {
return nil, err
}
var m fission.Metadata
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) EnvironmentGet(m *fission.Metadata) (*fission.Environment, error) {
relativeUrl := fmt.Sprintf("environments/%v", m.Name)
if len(m.Uid) > 0 {
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
}
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var env fission.Environment
err = json.Unmarshal(body, &env)
if err != nil {
return nil, err
}
return &env, nil
}
func (c *Client) EnvironmentUpdate(env *fission.Environment) (*fission.Metadata, error) {
reqbody, err := json.Marshal(env)
if err != nil {
return nil, err
}
relativeUrl := fmt.Sprintf("environments/%v", env.Metadata.Name)
resp, err := c.put(relativeUrl, "application/json", reqbody)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var m fission.Metadata
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) EnvironmentDelete(m *fission.Metadata) error {
relativeUrl := fmt.Sprintf("environments/%v", m.Name)
if len(m.Uid) > 0 {
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
}
err := c.delete(relativeUrl)
return err
}
func (c *Client) EnvironmentList() ([]fission.Environment, error) {
resp, err := http.Get(c.url("environments"))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
envs := make([]fission.Environment, 0)
err = json.Unmarshal(body, &envs)
if err != nil {
return nil, err
}
return envs, nil
}
func (c *Client) WatchCreate(w *fission.Watch) (*fission.Metadata, error) {
reqbody, err := json.Marshal(w)
if err != nil {
return nil, err
}
resp, err := http.Post(c.url("watches"), "application/json", bytes.NewReader(reqbody))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleCreateResponse(resp)
if err != nil {
return nil, err
}
var m fission.Metadata
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) WatchGet(m *fission.Metadata) (*fission.Watch, error) {
relativeUrl := fmt.Sprintf("watches/%v", m.Name)
if len(m.Uid) > 0 {
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
}
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var w fission.Watch
err = json.Unmarshal(body, &w)
if err != nil {
return nil, err
}
return &w, nil
}
func (c *Client) WatchUpdate(w *fission.Watch) (*fission.Metadata, error) {
return nil, fission.MakeError(fission.ErrorNotImplmented,
"watch update not implemented")
}
func (c *Client) WatchDelete(m *fission.Metadata) error {
relativeUrl := fmt.Sprintf("watches/%v", m.Name)
if len(m.Uid) > 0 {
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
}
err := c.delete(relativeUrl)
return err
}
func (c *Client) WatchList() ([]fission.Watch, error) {
resp, err := http.Get(c.url("watches"))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
watches := make([]fission.Watch, 0)
err = json.Unmarshal(body, &watches)
if err != nil {
return nil, err
}
return watches, err
}
func (c *Client) TimeTriggerCreate(t *fission.TimeTrigger) (*fission.Metadata, error) {
reqbody, err := json.Marshal(t)
if err != nil {
return nil, err
}
resp, err := http.Post(c.url("triggers/time"), "application/json", bytes.NewReader(reqbody))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleCreateResponse(resp)
if err != nil {
return nil, err
}
var m fission.Metadata
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) TimeTriggerGet(m *fission.Metadata) (*fission.TimeTrigger, error) {
relativeUrl := fmt.Sprintf("triggers/time/%v", m.Name)
if len(m.Uid) > 0 {
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
}
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var t fission.TimeTrigger
err = json.Unmarshal(body, &t)
if err != nil {
return nil, err
}
return &t, nil
}
func (c *Client) TimeTriggerUpdate(t *fission.TimeTrigger) (*fission.Metadata, error) {
reqbody, err := json.Marshal(t)
if err != nil {
return nil, err
}
relativeUrl := fmt.Sprintf("triggers/time/%v", t.Metadata.Name)
resp, err := c.put(relativeUrl, "application/json", reqbody)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var m fission.Metadata
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) TimeTriggerDelete(m *fission.Metadata) error {
relativeUrl := fmt.Sprintf("triggers/time/%v", m.Name)
if len(m.Uid) > 0 {
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
}
err := c.delete(relativeUrl)
return err
}
func (c *Client) TimeTriggerList() ([]fission.TimeTrigger, error) {
resp, err := http.Get(c.url("triggers/time"))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
triggers := make([]fission.TimeTrigger, 0)
err = json.Unmarshal(body, &triggers)
if err != nil {
return nil, err
}
return triggers, nil
}
+132
View File
@@ -0,0 +1,132 @@
/*
Copyright 2016 The Fission Authors.
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 client
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"k8s.io/client-go/1.5/pkg/api"
"github.com/fission/fission/tpr"
)
func (c *Client) EnvironmentCreate(env *tpr.Environment) (*api.ObjectMeta, error) {
reqbody, err := json.Marshal(env)
if err != nil {
return nil, err
}
resp, err := http.Post(c.url("environments"), "application/json", bytes.NewReader(reqbody))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleCreateResponse(resp)
if err != nil {
return nil, err
}
var m api.ObjectMeta
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) EnvironmentGet(m *api.ObjectMeta) (*tpr.Environment, error) {
relativeUrl := fmt.Sprintf("environments/%v", m.Name)
relativeUrl += fmt.Sprintf("?namespace=%v", m.Namespace)
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var env tpr.Environment
err = json.Unmarshal(body, &env)
if err != nil {
return nil, err
}
return &env, nil
}
func (c *Client) EnvironmentUpdate(env *tpr.Environment) (*api.ObjectMeta, error) {
reqbody, err := json.Marshal(env)
if err != nil {
return nil, err
}
relativeUrl := fmt.Sprintf("environments/%v", env.Metadata.Name)
resp, err := c.put(relativeUrl, "application/json", reqbody)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var m api.ObjectMeta
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) EnvironmentDelete(m *api.ObjectMeta) error {
relativeUrl := fmt.Sprintf("environments/%v", m.Name)
relativeUrl += fmt.Sprintf("?namespace=%v", m.Namespace)
return c.delete(relativeUrl)
}
func (c *Client) EnvironmentList() ([]tpr.Environment, error) {
resp, err := http.Get(c.url("environments"))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
envs := make([]tpr.Environment, 0)
err = json.Unmarshal(body, &envs)
if err != nil {
return nil, err
}
return envs, nil
}
+146
View File
@@ -0,0 +1,146 @@
/*
Copyright 2016 The Fission Authors.
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 client
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"k8s.io/client-go/1.5/pkg/api"
"github.com/fission/fission/tpr"
)
func (c *Client) FunctionCreate(f *tpr.Function) (*api.ObjectMeta, error) {
reqbody, err := json.Marshal(f)
if err != nil {
return nil, err
}
resp, err := http.Post(c.url("functions"), "application/json", bytes.NewReader(reqbody))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleCreateResponse(resp)
if err != nil {
return nil, err
}
var m api.ObjectMeta
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) FunctionGet(m *api.ObjectMeta) (*tpr.Function, error) {
relativeUrl := fmt.Sprintf("functions/%v", m.Name)
relativeUrl += fmt.Sprintf("?namespace=%v", m.Namespace)
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var f tpr.Function
err = json.Unmarshal(body, &f)
if err != nil {
return nil, err
}
return &f, nil
}
func (c *Client) FunctionGetRawDeployment(m *api.ObjectMeta) ([]byte, error) {
relativeUrl := fmt.Sprintf("functions/%v", m.Name)
relativeUrl += fmt.Sprintf("?namespace=%v", m.Namespace)
relativeUrl += fmt.Sprintf("&deploymentraw=1")
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
defer resp.Body.Close()
return c.handleResponse(resp)
}
func (c *Client) FunctionUpdate(f *tpr.Function) (*api.ObjectMeta, error) {
reqbody, err := json.Marshal(f)
if err != nil {
return nil, err
}
relativeUrl := fmt.Sprintf("functions/%v", f.Metadata.Name)
resp, err := c.put(relativeUrl, "application/json", reqbody)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var m api.ObjectMeta
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) FunctionDelete(m *api.ObjectMeta) error {
relativeUrl := fmt.Sprintf("functions/%v", m.Name)
relativeUrl += fmt.Sprintf("?namespace=%v", m.Namespace)
return c.delete(relativeUrl)
}
func (c *Client) FunctionList() ([]tpr.Function, error) {
resp, err := http.Get(c.url("functions"))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
funcs := make([]tpr.Function, 0)
err = json.Unmarshal(body, &funcs)
if err != nil {
return nil, err
}
return funcs, nil
}
+131
View File
@@ -0,0 +1,131 @@
/*
Copyright 2016 The Fission Authors.
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 client
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"k8s.io/client-go/1.5/pkg/api"
"github.com/fission/fission/tpr"
)
func (c *Client) HTTPTriggerCreate(t *tpr.Httptrigger) (*api.ObjectMeta, error) {
reqbody, err := json.Marshal(t)
if err != nil {
return nil, err
}
resp, err := http.Post(c.url("triggers/http"), "application/json", bytes.NewReader(reqbody))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleCreateResponse(resp)
if err != nil {
return nil, err
}
var m api.ObjectMeta
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) HTTPTriggerGet(m *api.ObjectMeta) (*tpr.Httptrigger, error) {
relativeUrl := fmt.Sprintf("triggers/http/%v", m.Name)
relativeUrl += fmt.Sprintf("?namespace=%v", m.Namespace)
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var t tpr.Httptrigger
err = json.Unmarshal(body, &t)
if err != nil {
return nil, err
}
return &t, nil
}
func (c *Client) HTTPTriggerUpdate(t *tpr.Httptrigger) (*api.ObjectMeta, error) {
reqbody, err := json.Marshal(t)
if err != nil {
return nil, err
}
relativeUrl := fmt.Sprintf("triggers/http/%v", t.Metadata.Name)
resp, err := c.put(relativeUrl, "application/json", reqbody)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var m api.ObjectMeta
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) HTTPTriggerDelete(m *api.ObjectMeta) error {
relativeUrl := fmt.Sprintf("triggers/http/%v", m.Name)
relativeUrl += fmt.Sprintf("?namespace=%v", m.Namespace)
return c.delete(relativeUrl)
}
func (c *Client) HTTPTriggerList() ([]tpr.Httptrigger, error) {
resp, err := http.Get(c.url("triggers/http"))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
triggers := make([]tpr.Httptrigger, 0)
err = json.Unmarshal(body, &triggers)
if err != nil {
return nil, err
}
return triggers, nil
}
+111
View File
@@ -0,0 +1,111 @@
/*
Copyright 2016 The Fission Authors.
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 client
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"k8s.io/client-go/1.5/pkg/api"
"github.com/fission/fission"
"github.com/fission/fission/tpr"
)
func (c *Client) WatchCreate(w *tpr.Kuberneteswatchtrigger) (*api.ObjectMeta, error) {
reqbody, err := json.Marshal(w)
if err != nil {
return nil, err
}
resp, err := http.Post(c.url("watches"), "application/json", bytes.NewReader(reqbody))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleCreateResponse(resp)
if err != nil {
return nil, err
}
var m api.ObjectMeta
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) WatchGet(m *api.ObjectMeta) (*tpr.Kuberneteswatchtrigger, error) {
relativeUrl := fmt.Sprintf("watches/%v", m.Name)
relativeUrl += fmt.Sprintf("?namespace=%v", m.Namespace)
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var w tpr.Kuberneteswatchtrigger
err = json.Unmarshal(body, &w)
if err != nil {
return nil, err
}
return &w, nil
}
func (c *Client) WatchUpdate(w *tpr.Kuberneteswatchtrigger) (*api.ObjectMeta, error) {
return nil, fission.MakeError(fission.ErrorNotImplmented,
"watch update not implemented")
}
func (c *Client) WatchDelete(m *api.ObjectMeta) error {
relativeUrl := fmt.Sprintf("watches/%v", m.Name)
relativeUrl += fmt.Sprintf("?namespace=%v", m.Namespace)
return c.delete(relativeUrl)
}
func (c *Client) WatchList() ([]tpr.Kuberneteswatchtrigger, error) {
resp, err := http.Get(c.url("watches"))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
watches := make([]tpr.Kuberneteswatchtrigger, 0)
err = json.Unmarshal(body, &watches)
if err != nil {
return nil, err
}
return watches, err
}
+137
View File
@@ -0,0 +1,137 @@
/*
Copyright 2016 The Fission Authors.
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 client
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"k8s.io/client-go/1.5/pkg/api"
"github.com/fission/fission/tpr"
)
func (c *Client) MessageQueueTriggerCreate(t *tpr.Messagequeuetrigger) (*api.ObjectMeta, error) {
reqbody, err := json.Marshal(t)
if err != nil {
return nil, err
}
resp, err := http.Post(c.url("triggers/messagequeue"), "application/json", bytes.NewReader(reqbody))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleCreateResponse(resp)
if err != nil {
return nil, err
}
var m api.ObjectMeta
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) MessageQueueTriggerGet(m *api.ObjectMeta) (*tpr.Messagequeuetrigger, error) {
relativeUrl := fmt.Sprintf("triggers/messagequeue/%v", m.Name)
relativeUrl += fmt.Sprintf("?namespace=%v", m.Namespace)
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var t tpr.Messagequeuetrigger
err = json.Unmarshal(body, &t)
if err != nil {
return nil, err
}
return &t, nil
}
func (c *Client) MessageQueueTriggerUpdate(mqTrigger *tpr.Messagequeuetrigger) (*api.ObjectMeta, error) {
reqbody, err := json.Marshal(mqTrigger)
if err != nil {
return nil, err
}
relativeUrl := fmt.Sprintf("triggers/messagequeue/%v", mqTrigger.Metadata.Name)
resp, err := c.put(relativeUrl, "application/json", reqbody)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var m api.ObjectMeta
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) MessageQueueTriggerDelete(m *api.ObjectMeta) error {
relativeUrl := fmt.Sprintf("triggers/messagequeue/%v", m.Name)
relativeUrl += fmt.Sprintf("?namespace=%v", m.Namespace)
return c.delete(relativeUrl)
}
func (c *Client) MessageQueueTriggerList(mqType string) ([]tpr.Messagequeuetrigger, error) {
relativeUrl := "triggers/messagequeue"
if len(mqType) > 0 {
// TODO remove this, replace with field selector
relativeUrl += fmt.Sprintf("?mqtype=%v", mqType)
}
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
triggers := make([]tpr.Messagequeuetrigger, 0)
err = json.Unmarshal(body, &triggers)
if err != nil {
return nil, err
}
return triggers, nil
}
+132
View File
@@ -0,0 +1,132 @@
/*
Copyright 2016 The Fission Authors.
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 client
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"k8s.io/client-go/1.5/pkg/api"
"github.com/fission/fission/tpr"
)
func (c *Client) PackageCreate(f *tpr.Package) (*api.ObjectMeta, error) {
reqbody, err := json.Marshal(f)
if err != nil {
return nil, err
}
resp, err := http.Post(c.url("packages"), "application/json", bytes.NewReader(reqbody))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleCreateResponse(resp)
if err != nil {
return nil, err
}
var m api.ObjectMeta
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) PackageGet(m *api.ObjectMeta) (*tpr.Package, error) {
relativeUrl := fmt.Sprintf("packages/%v", m.Name)
relativeUrl += fmt.Sprintf("?namespace=%v", m.Namespace)
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var f tpr.Package
err = json.Unmarshal(body, &f)
if err != nil {
return nil, err
}
return &f, nil
}
func (c *Client) PackageUpdate(f *tpr.Package) (*api.ObjectMeta, error) {
reqbody, err := json.Marshal(f)
if err != nil {
return nil, err
}
relativeUrl := fmt.Sprintf("packages/%v", f.Metadata.Name)
resp, err := c.put(relativeUrl, "application/json", reqbody)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var m api.ObjectMeta
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) PackageDelete(m *api.ObjectMeta) error {
relativeUrl := fmt.Sprintf("packages/%v", m.Name)
relativeUrl += fmt.Sprintf("?namespace=%v", m.Namespace)
return c.delete(relativeUrl)
}
func (c *Client) PackageList() ([]tpr.Package, error) {
resp, err := http.Get(c.url("packages"))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
funcs := make([]tpr.Package, 0)
err = json.Unmarshal(body, &funcs)
if err != nil {
return nil, err
}
return funcs, nil
}
+131
View File
@@ -0,0 +1,131 @@
/*
Copyright 2016 The Fission Authors.
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 client
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"k8s.io/client-go/1.5/pkg/api"
"github.com/fission/fission/tpr"
)
func (c *Client) TimeTriggerCreate(t *tpr.Timetrigger) (*api.ObjectMeta, error) {
reqbody, err := json.Marshal(t)
if err != nil {
return nil, err
}
resp, err := http.Post(c.url("triggers/time"), "application/json", bytes.NewReader(reqbody))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleCreateResponse(resp)
if err != nil {
return nil, err
}
var m api.ObjectMeta
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) TimeTriggerGet(m *api.ObjectMeta) (*tpr.Timetrigger, error) {
relativeUrl := fmt.Sprintf("triggers/time/%v", m.Name)
relativeUrl += fmt.Sprintf("?namespace=%v", m.Namespace)
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var t tpr.Timetrigger
err = json.Unmarshal(body, &t)
if err != nil {
return nil, err
}
return &t, nil
}
func (c *Client) TimeTriggerUpdate(t *tpr.Timetrigger) (*api.ObjectMeta, error) {
reqbody, err := json.Marshal(t)
if err != nil {
return nil, err
}
relativeUrl := fmt.Sprintf("triggers/time/%v", t.Metadata.Name)
resp, err := c.put(relativeUrl, "application/json", reqbody)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var m api.ObjectMeta
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) TimeTriggerDelete(m *api.ObjectMeta) error {
relativeUrl := fmt.Sprintf("triggers/time/%v", m.Name)
relativeUrl += fmt.Sprintf("?namespace=%v", m.Namespace)
return c.delete(relativeUrl)
}
func (c *Client) TimeTriggerList() ([]tpr.Timetrigger, error) {
resp, err := http.Get(c.url("triggers/time"))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
triggers := make([]tpr.Timetrigger, 0)
err = json.Unmarshal(body, &triggers)
if err != nil {
return nil, err
}
return triggers, nil
}
@@ -17,16 +17,27 @@ limitations under the License.
package controller
import (
"encoding/json"
"log"
"github.com/fission/fission/tpr"
)
type JsonSerializer struct {
}
func Start(port int) {
fc, kc, err := tpr.MakeFissionClient()
if err != nil {
log.Fatalf("Failed to connect to K8s API: %v", err)
}
func (j JsonSerializer) serialize(r resource) ([]byte, error) {
return json.Marshal(r)
}
err = tpr.EnsureFissionTPRs(kc)
if err != nil {
log.Fatalf("Failed to create fission TPRs: %v", err)
}
func (j JsonSerializer) deserialize(buf []byte, r resource) error {
return json.Unmarshal(buf, r)
fc.WaitForTPRs()
api, err := MakeAPI()
if err != nil {
log.Fatalf("Failed to start controller: %v", err)
}
api.Serve(port)
}
+51 -46
View File
@@ -23,133 +23,138 @@ import (
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"k8s.io/client-go/1.5/pkg/api"
"github.com/fission/fission"
"github.com/fission/fission/tpr"
)
func (api *API) EnvironmentApiList(w http.ResponseWriter, r *http.Request) {
envs, err := api.EnvironmentStore.List()
func (a *API) EnvironmentApiList(w http.ResponseWriter, r *http.Request) {
envs, err := a.fissionClient.Environments(api.NamespaceAll).List(api.ListOptions{})
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(envs)
resp, err := json.Marshal(envs.Items)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
a.respondWithSuccess(w, resp)
}
func (api *API) EnvironmentApiCreate(w http.ResponseWriter, r *http.Request) {
func (a *API) EnvironmentApiCreate(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
var env fission.Environment
var env tpr.Environment
err = json.Unmarshal(body, &env)
if err != nil {
log.Printf("Failed to unmarshal request body: [%v]", body)
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
uid, err := api.EnvironmentStore.Create(&env)
err = validateResourceName(env.Metadata.Name)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
m := &fission.Metadata{Name: env.Metadata.Name, Uid: uid}
resp, err := json.Marshal(m)
enew, err := a.fissionClient.Environments(env.Metadata.Namespace).Create(&env)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(enew.Metadata)
if err != nil {
a.respondWithError(w, err)
return
}
w.WriteHeader(http.StatusCreated)
api.respondWithSuccess(w, resp)
a.respondWithSuccess(w, resp)
}
func (api *API) EnvironmentApiGet(w http.ResponseWriter, r *http.Request) {
var m fission.Metadata
func (a *API) EnvironmentApiGet(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
m.Name = vars["environment"]
m.Uid = r.FormValue("uid") // empty if uid is absent
name := vars["environment"]
ns := vars["namespace"]
if len(ns) == 0 {
ns = api.NamespaceDefault
}
env, err := api.EnvironmentStore.Get(&m)
env, err := a.fissionClient.Environments(ns).Get(name)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(env)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
a.respondWithSuccess(w, resp)
}
func (api *API) EnvironmentApiUpdate(w http.ResponseWriter, r *http.Request) {
func (a *API) EnvironmentApiUpdate(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["environment"]
body, err := ioutil.ReadAll(r.Body)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
var env fission.Environment
var env tpr.Environment
err = json.Unmarshal(body, &env)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
if name != env.Metadata.Name {
err = fission.MakeError(fission.ErrorInvalidArgument, "Environment name doesn't match URL")
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
uid, err := api.EnvironmentStore.Update(&env)
enew, err := a.fissionClient.Environments(env.Metadata.Namespace).Update(&env)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
m := &fission.Metadata{Name: env.Metadata.Name, Uid: uid}
resp, err := json.Marshal(m)
resp, err := json.Marshal(enew.Metadata)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
a.respondWithSuccess(w, resp)
}
func (api *API) EnvironmentApiDelete(w http.ResponseWriter, r *http.Request) {
func (a *API) EnvironmentApiDelete(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
var m fission.Metadata
m.Name = vars["environment"]
m.Uid = r.FormValue("uid") // empty if uid is absent
if len(m.Uid) == 0 {
log.WithFields(log.Fields{"httpTrigger": m.Name}).Info("Deleting all versions")
name := vars["environment"]
ns := vars["namespace"]
if len(ns) == 0 {
ns = api.NamespaceDefault
}
err := api.EnvironmentStore.Delete(m)
err := a.fissionClient.Environments(ns).Delete(name, &api.DeleteOptions{})
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
api.respondWithSuccess(w, []byte(""))
a.respondWithSuccess(w, []byte(""))
}
-79
View File
@@ -1,79 +0,0 @@
/*
Copyright 2016 The Fission Authors.
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 controller
import (
"github.com/satori/go.uuid"
"github.com/fission/fission"
)
type EnvironmentStore struct {
ResourceStore
}
func (es *EnvironmentStore) Create(e *fission.Environment) (string, error) {
e.Metadata.Uid = uuid.NewV4().String()
return e.Metadata.Uid, es.ResourceStore.create(e)
}
func (es *EnvironmentStore) Get(m *fission.Metadata) (*fission.Environment, error) {
var e fission.Environment
err := es.ResourceStore.read(m.Name, &e)
if err != nil {
return nil, err
}
return &e, nil
}
func (es *EnvironmentStore) Update(e *fission.Environment) (string, error) {
e.Metadata.Uid = uuid.NewV4().String()
return e.Metadata.Uid, es.ResourceStore.update(e)
}
func (es *EnvironmentStore) Delete(m fission.Metadata) error {
typeName, err := getTypeName(fission.Environment{})
if err != nil {
return err
}
return es.ResourceStore.delete(typeName, m.Name)
}
func (es *EnvironmentStore) List() ([]fission.Environment, error) {
typeName, err := getTypeName(fission.Environment{})
if err != nil {
return nil, err
}
bufs, err := es.ResourceStore.getAll(typeName)
if err != nil {
return nil, err
}
envs := make([]fission.Environment, 0, len(bufs))
js := JsonSerializer{}
for _, buf := range bufs {
var e fission.Environment
err = js.deserialize([]byte(buf), &e)
if err != nil {
return nil, err
}
envs = append(envs, e)
}
return envs, nil
}
-134
View File
@@ -1,134 +0,0 @@
/*
Copyright 2016 The Fission Authors.
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 controller
import (
"io/ioutil"
"os"
"path"
log "github.com/sirupsen/logrus"
)
type requestType int
const (
READ requestType = iota
WRITE
DELETE
)
type (
FileStore struct {
root string // abs path of root of filestore
requestChannel chan fileStoreRequest
}
fileStoreRequest struct {
requestType
fileName string // relative path
fileContents []byte
responseChannel chan fileStoreResponse
}
fileStoreResponse struct {
error
fileContents []byte
}
)
func MakeFileStore(path string) *FileStore {
// create directory if necessary
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
err = os.MkdirAll(path, os.ModeDir|0700)
if err != nil {
log.Printf("Failed to create directory %v", path)
return nil
}
log.Printf("Created directory %v", path)
} else {
log.Printf("Unknown error initializing filestore: %v", err)
return nil
}
}
fileStore := &FileStore{
root: path,
requestChannel: make(chan fileStoreRequest),
}
go fileStore.fileStoreService()
return fileStore
}
func (fs *FileStore) fileStoreService() {
for {
req := <-fs.requestChannel
response := &fileStoreResponse{}
log.WithFields(log.Fields{"file": req.fileName, "type": req.requestType}).Debug("fileStore request")
switch req.requestType {
case READ:
response.fileContents, response.error = ioutil.ReadFile(path.Join(fs.root, req.fileName))
case WRITE:
response.error = ioutil.WriteFile(path.Join(fs.root, req.fileName), req.fileContents, 0600)
case DELETE:
response.error = os.Remove(path.Join(fs.root, req.fileName))
if os.IsNotExist(response.error) {
response.error = nil
}
default:
log.Panic("bad request")
}
req.responseChannel <- *response
}
}
func (fs *FileStore) read(fileName string) ([]byte, error) {
req := fileStoreRequest{
requestType: READ,
fileName: fileName,
responseChannel: make(chan fileStoreResponse),
}
fs.requestChannel <- req
response := <-req.responseChannel
return response.fileContents, response.error
}
func (fs *FileStore) write(fileName string, contents []byte) error {
req := fileStoreRequest{
requestType: WRITE,
fileName: fileName,
fileContents: contents,
responseChannel: make(chan fileStoreResponse),
}
fs.requestChannel <- req
response := <-req.responseChannel
return response.error
}
func (fs *FileStore) delete(fileName string) error {
req := fileStoreRequest{
requestType: DELETE,
fileName: fileName,
responseChannel: make(chan fileStoreResponse),
}
fs.requestChannel <- req
response := <-req.responseChannel
return response.error
}
-63
View File
@@ -1,63 +0,0 @@
/*
Copyright 2016 The Fission Authors.
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 controller
import (
"io/ioutil"
"log"
"os"
"testing"
)
func TestFileStore(t *testing.T) {
// tmp dir
dir, err := ioutil.TempDir("", "testFileStore")
if err != nil {
t.Fatalf("error creating temp dir: %v", err)
}
defer os.RemoveAll(dir)
log.Printf("temp dir at %v", dir)
// file store
fs := MakeFileStore(dir)
_, err = fs.read("nonexistent")
if err == nil {
t.Fatalf("expected an error")
}
path := "fileStoreTest"
contents := []byte("bar")
err = fs.write(path, contents)
if err != nil {
t.Fatalf("error: %v", err)
}
observedContents, err := fs.read(path)
if err != nil {
t.Fatalf("error: %v", err)
}
if string(observedContents) != string(contents) {
t.Fatalf("contents don't match")
}
err = fs.delete(path)
if err != nil {
t.Fatalf("error: %v", err)
}
}
+58 -75
View File
@@ -17,7 +17,6 @@ limitations under the License.
package controller
import (
"encoding/base64"
"encoding/json"
"io/ioutil"
"net/http"
@@ -26,165 +25,149 @@ import (
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"k8s.io/client-go/1.5/pkg/api"
"github.com/fission/fission"
"github.com/fission/fission/tpr"
)
func (api *API) FunctionApiList(w http.ResponseWriter, r *http.Request) {
funcs, err := api.FunctionStore.List()
func (a *API) FunctionApiList(w http.ResponseWriter, r *http.Request) {
funcs, err := a.fissionClient.Functions(api.NamespaceAll).List(api.ListOptions{})
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(funcs)
resp, err := json.Marshal(funcs.Items)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
a.respondWithSuccess(w, resp)
}
func (api *API) FunctionApiCreate(w http.ResponseWriter, r *http.Request) {
func (a *API) FunctionApiCreate(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
var f fission.Function
var f tpr.Function
err = json.Unmarshal(body, &f)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
dec, err := base64.StdEncoding.DecodeString(f.Code)
err = validateResourceName(f.Metadata.Name)
if err != nil {
api.respondWithError(w, err)
return
}
f.Code = string(dec)
uid, err := api.FunctionStore.Create(&f)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
m := &fission.Metadata{Name: f.Name, Uid: uid}
resp, err := json.Marshal(m)
fnew, err := a.fissionClient.Functions(f.Metadata.Namespace).Create(&f)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(fnew.Metadata)
if err != nil {
a.respondWithError(w, err)
return
}
w.WriteHeader(http.StatusCreated)
api.respondWithSuccess(w, resp)
a.respondWithSuccess(w, resp)
}
func (api *API) FunctionApiGet(w http.ResponseWriter, r *http.Request) {
var m fission.Metadata
func (a *API) FunctionApiGet(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
m.Name = vars["function"]
m.Uid = r.FormValue("uid") // empty if uid is absent
raw := r.FormValue("raw") // just the code
name := vars["function"]
ns := vars["namespace"]
if len(ns) == 0 {
ns = api.NamespaceDefault
}
f, err := api.FunctionStore.Get(&m)
f, err := a.fissionClient.Functions(ns).Get(name)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
var resp []byte
if raw != "" {
resp = []byte(f.Code)
} else {
f.Code = base64.StdEncoding.EncodeToString([]byte(f.Code))
resp, err = json.Marshal(f)
if err != nil {
api.respondWithError(w, err)
return
}
resp, err := json.Marshal(f)
if err != nil {
a.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
a.respondWithSuccess(w, resp)
}
func (api *API) FunctionApiUpdate(w http.ResponseWriter, r *http.Request) {
func (a *API) FunctionApiUpdate(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
funcName := vars["function"]
name := vars["function"]
body, err := ioutil.ReadAll(r.Body)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
var f fission.Function
var f tpr.Function
err = json.Unmarshal(body, &f)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
if funcName != f.Metadata.Name {
if name != f.Metadata.Name {
err = fission.MakeError(fission.ErrorInvalidArgument, "Function name doesn't match URL")
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
dec, err := base64.StdEncoding.DecodeString(f.Code)
fnew, err := a.fissionClient.Functions(f.Metadata.Namespace).Update(&f)
if err != nil {
api.respondWithError(w, err)
return
}
f.Code = string(dec)
uid, err := api.FunctionStore.Update(&f)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
m := &fission.Metadata{Name: f.Name, Uid: uid}
resp, err := json.Marshal(m)
resp, err := json.Marshal(fnew.Metadata)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
a.respondWithSuccess(w, resp)
}
func (api *API) FunctionApiDelete(w http.ResponseWriter, r *http.Request) {
func (a *API) FunctionApiDelete(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
var m fission.Metadata
m.Name = vars["function"]
m.Uid = r.FormValue("uid") // empty if uid is absent
if len(m.Uid) == 0 {
log.WithFields(log.Fields{"function": m.Name}).Info("Deleting all versions")
name := vars["function"]
ns := vars["namespace"]
if len(ns) == 0 {
ns = api.NamespaceDefault
}
err := api.FunctionStore.Delete(m)
err := a.fissionClient.Functions(ns).Delete(name, &api.DeleteOptions{})
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
api.respondWithSuccess(w, []byte(""))
a.respondWithSuccess(w, []byte(""))
}
// FunctionLogsApiPost establishes a proxy server to log database, and redirect
// query command send from client to database then proxy back the db response.
func (api *API) FunctionLogsApiPost(w http.ResponseWriter, r *http.Request) {
func (a *API) FunctionLogsApiPost(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
// get dbType from url
dbType := vars["dbType"]
// find correspond db http url
dbCnf := api.getLogDBConfig(dbType)
dbCnf := a.getLogDBConfig(dbType)
svcUrl, err := url.Parse(dbCnf.httpURL)
if err != nil {
-156
View File
@@ -1,156 +0,0 @@
/*
Copyright 2016 The Fission Authors.
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 controller
import (
log "github.com/sirupsen/logrus"
"github.com/fission/fission"
)
type FunctionStore struct {
ResourceStore
}
func (fs *FunctionStore) Create(f *fission.Function) (string, error) {
code := []byte(f.Code)
_, uid, err := fs.ResourceStore.writeFile(f.Key(), code)
if err != nil {
return "", err
}
f.Metadata.Uid = uid
f.Code = ""
err = fs.ResourceStore.create(f)
if err != nil {
fs.ResourceStore.deleteFile(f.Key(), uid) // ignore errors
return "", err
}
return f.Metadata.Uid, nil
}
func (fs *FunctionStore) Get(m *fission.Metadata) (*fission.Function, error) {
var f fission.Function
err := fs.ResourceStore.read(m.Name, &f)
if err != nil {
return nil, err
}
var code []byte
if len(m.Uid) > 0 {
log.WithFields(log.Fields{"Uid": m.Uid}).Info("fetching by uid")
code, err = fs.ResourceStore.readFile(m.Name, &m.Uid)
f.Metadata = *m
} else {
code, err = fs.ResourceStore.readFile(m.Name, nil)
}
if err != nil {
return nil, err
}
f.Code = string(code)
return &f, nil
}
func (fs *FunctionStore) Update(f *fission.Function) (string, error) {
code := []byte(f.Code)
_, uid, err := fs.ResourceStore.writeFile(f.Key(), code)
if err != nil {
return "", err
}
var fnew fission.Function
err = fs.ResourceStore.read(f.Metadata.Name, &fnew)
if err != nil {
fs.ResourceStore.deleteFile(f.Key(), uid) // ignore err
return "", err
}
fnew.Metadata.Uid = uid
fnew.Environment = f.Environment
err = fs.ResourceStore.update(fnew)
if err != nil {
fs.ResourceStore.deleteFile(f.Key(), uid) // ignore err
return "", err
}
return uid, err
}
func (fs *FunctionStore) Delete(m fission.Metadata) error {
if len(m.Uid) == 0 {
err := fs.ResourceStore.deleteAllFiles(m.Name)
if err != nil {
return err
}
} else {
err := fs.ResourceStore.deleteFile(m.Name, m.Uid)
if err != nil {
return err
}
}
typeName, err := getTypeName(fission.Function{})
if err != nil {
return err
}
bufs, err := fs.ResourceStore.getAll("file/" + m.Name)
if err != nil {
return err
}
if len(bufs) == 0 {
return fs.ResourceStore.delete(typeName, m.Name)
}
fnew, err := fs.Get(&fission.Metadata{Name: m.Name})
if err != nil {
return err
}
latestUid := bufs[len(bufs)-1] // function always tracks the latest version of code
if latestUid == fnew.Uid {
return nil
}
fnew.Uid = latestUid
return fs.ResourceStore.update(fnew)
}
func (fs *FunctionStore) List() ([]fission.Function, error) {
typeName, err := getTypeName(fission.Function{})
if err != nil {
return nil, err
}
bufs, err := fs.ResourceStore.getAll(typeName)
if err != nil {
return nil, err
}
js := JsonSerializer{}
functions := make([]fission.Function, 0, len(bufs))
for _, buf := range bufs {
var f fission.Function
err = js.deserialize([]byte(buf), &f)
if err != nil {
return nil, err
}
functions = append(functions, f)
}
return functions, nil
}
+85 -66
View File
@@ -18,151 +18,170 @@ package controller
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"k8s.io/client-go/1.5/pkg/api"
"github.com/fission/fission"
"github.com/fission/fission/tpr"
)
func (api *API) HTTPTriggerApiList(w http.ResponseWriter, r *http.Request) {
triggers, err := api.HTTPTriggerStore.List()
func (a *API) HTTPTriggerApiList(w http.ResponseWriter, r *http.Request) {
triggers, err := a.fissionClient.Httptriggers(api.NamespaceAll).List(api.ListOptions{})
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(triggers)
resp, err := json.Marshal(triggers.Items)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
a.respondWithSuccess(w, resp)
}
func (api *API) HTTPTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
func (a *API) checkHttpTriggerDuplicates(t *tpr.Httptrigger) error {
triggers, err := a.fissionClient.Httptriggers(api.NamespaceAll).List(api.ListOptions{})
if err != nil {
api.respondWithError(w, err)
return
return err
}
var t fission.HTTPTrigger
err = json.Unmarshal(body, &t)
if err != nil {
api.respondWithError(w, err)
return
}
triggers, err := api.HTTPTriggerStore.List()
if err != nil {
api.respondWithError(w, err)
return
}
for _, url := range triggers {
if url.UrlPattern == t.UrlPattern && url.Method == t.Method {
err = fission.MakeError(fission.ErrorNameExists,
"HTTPTrigger with same URL & method already exists")
api.respondWithError(w, err)
return
for _, ht := range triggers.Items {
if ht.Spec.RelativeURL == t.Spec.RelativeURL && ht.Spec.Method == t.Spec.Method {
return fission.MakeError(fission.ErrorNameExists,
fmt.Sprintf("HTTPTrigger with same URL & method already exists (%v)",
ht.Metadata.Name))
}
}
return nil
}
uid, err := api.HTTPTriggerStore.Create(&t)
func (a *API) HTTPTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
m := &fission.Metadata{Name: t.Metadata.Name, Uid: uid}
resp, err := json.Marshal(m)
var t tpr.Httptrigger
err = json.Unmarshal(body, &t)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
err = validateResourceName(t.Metadata.Name)
if err != nil {
a.respondWithError(w, err)
return
}
// Ensure we don't have a duplicate HTTP route defined (same URL and method)
err = a.checkHttpTriggerDuplicates(&t)
if err != nil {
a.respondWithError(w, err)
return
}
tnew, err := a.fissionClient.Httptriggers(t.Metadata.Namespace).Create(&t)
if err != nil {
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(tnew.Metadata)
if err != nil {
a.respondWithError(w, err)
return
}
w.WriteHeader(http.StatusCreated)
api.respondWithSuccess(w, resp)
a.respondWithSuccess(w, resp)
}
func (api *API) HTTPTriggerApiGet(w http.ResponseWriter, r *http.Request) {
var m fission.Metadata
func (a *API) HTTPTriggerApiGet(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
m.Name = vars["httpTrigger"]
m.Uid = r.FormValue("uid") // empty if uid is absent
name := vars["httpTrigger"]
ns := vars["namespace"]
if len(ns) == 0 {
ns = api.NamespaceDefault
}
t, err := api.HTTPTriggerStore.Get(&m)
t, err := a.fissionClient.Httptriggers(ns).Get(name)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(t)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
a.respondWithSuccess(w, resp)
}
func (api *API) HTTPTriggerApiUpdate(w http.ResponseWriter, r *http.Request) {
func (a *API) HTTPTriggerApiUpdate(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["httpTrigger"]
body, err := ioutil.ReadAll(r.Body)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
var t fission.HTTPTrigger
var t tpr.Httptrigger
err = json.Unmarshal(body, &t)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
if name != t.Metadata.Name {
err = fission.MakeError(fission.ErrorInvalidArgument, "HTTPTrigger name doesn't match URL")
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
uid, err := api.HTTPTriggerStore.Update(&t)
err = a.checkHttpTriggerDuplicates(&t)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
m := &fission.Metadata{Name: t.Metadata.Name, Uid: uid}
resp, err := json.Marshal(m)
tnew, err := a.fissionClient.Httptriggers(t.Metadata.Namespace).Update(&t)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
resp, err := json.Marshal(tnew.Metadata)
if err != nil {
a.respondWithError(w, err)
return
}
a.respondWithSuccess(w, resp)
}
func (api *API) HTTPTriggerApiDelete(w http.ResponseWriter, r *http.Request) {
func (a *API) HTTPTriggerApiDelete(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
var m fission.Metadata
m.Name = vars["httpTrigger"]
m.Uid = r.FormValue("uid") // empty if uid is absent
if len(m.Uid) == 0 {
log.WithFields(log.Fields{"httpTrigger": m.Name}).Info("Deleting all versions")
name := vars["httpTrigger"]
ns := vars["namespace"]
if len(ns) == 0 {
ns = api.NamespaceDefault
}
err := api.HTTPTriggerStore.Delete(m)
err := a.fissionClient.Httptriggers(ns).Delete(name, &api.DeleteOptions{})
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
api.respondWithSuccess(w, []byte(""))
a.respondWithSuccess(w, []byte(""))
}
-79
View File
@@ -1,79 +0,0 @@
/*
Copyright 2016 The Fission Authors.
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 controller
import (
"github.com/satori/go.uuid"
"github.com/fission/fission"
)
type HTTPTriggerStore struct {
ResourceStore
}
func (hts *HTTPTriggerStore) Create(ht *fission.HTTPTrigger) (string, error) {
ht.Metadata.Uid = uuid.NewV4().String()
return ht.Metadata.Uid, hts.ResourceStore.create(ht)
}
func (hts *HTTPTriggerStore) Get(m *fission.Metadata) (*fission.HTTPTrigger, error) {
var ht fission.HTTPTrigger
err := hts.ResourceStore.read(m.Name, &ht)
if err != nil {
return nil, err
}
return &ht, nil
}
func (hts *HTTPTriggerStore) Update(ht *fission.HTTPTrigger) (string, error) {
ht.Metadata.Uid = uuid.NewV4().String()
return ht.Metadata.Uid, hts.ResourceStore.update(ht)
}
func (hts *HTTPTriggerStore) Delete(m fission.Metadata) error {
typeName, err := getTypeName(fission.HTTPTrigger{})
if err != nil {
return err
}
return hts.ResourceStore.delete(typeName, m.Name)
}
func (hts *HTTPTriggerStore) List() ([]fission.HTTPTrigger, error) {
typeName, err := getTypeName(fission.HTTPTrigger{})
if err != nil {
return nil, err
}
bufs, err := hts.ResourceStore.getAll(typeName)
if err != nil {
return nil, err
}
triggers := make([]fission.HTTPTrigger, 0, len(bufs))
js := JsonSerializer{}
for _, buf := range bufs {
var ht fission.HTTPTrigger
err = js.deserialize([]byte(buf), &ht)
if err != nil {
return nil, err
}
triggers = append(triggers, ht)
}
return triggers, nil
}
+153
View File
@@ -0,0 +1,153 @@
/*
Copyright 2017 The Fission Authors.
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 controller
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/gorilla/mux"
"k8s.io/client-go/1.5/pkg/api"
"github.com/fission/fission"
"github.com/fission/fission/tpr"
)
func (a *API) MessageQueueTriggerApiList(w http.ResponseWriter, r *http.Request) {
//mqType := r.FormValue("mqtype") // ignored for now
triggers, err := a.fissionClient.Messagequeuetriggers(api.NamespaceAll).List(api.ListOptions{})
if err != nil {
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(triggers.Items)
if err != nil {
a.respondWithError(w, err)
return
}
a.respondWithSuccess(w, resp)
}
func (a *API) MessageQueueTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
a.respondWithError(w, err)
return
}
var mqTrigger tpr.Messagequeuetrigger
err = json.Unmarshal(body, &mqTrigger)
if err != nil {
a.respondWithError(w, err)
return
}
err = validateResourceName(mqTrigger.Metadata.Name)
if err != nil {
a.respondWithError(w, err)
return
}
tnew, err := a.fissionClient.Messagequeuetriggers(mqTrigger.Metadata.Namespace).Create(&mqTrigger)
if err != nil {
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(tnew.Metadata)
if err != nil {
a.respondWithError(w, err)
return
}
w.WriteHeader(http.StatusCreated)
a.respondWithSuccess(w, resp)
}
func (a *API) MessageQueueTriggerApiGet(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["mqTrigger"]
ns := vars["namespace"]
if len(ns) == 0 {
ns = api.NamespaceDefault
}
mqTrigger, err := a.fissionClient.Messagequeuetriggers(ns).Get(name)
if err != nil {
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(mqTrigger)
if err != nil {
a.respondWithError(w, err)
return
}
a.respondWithSuccess(w, resp)
}
func (a *API) MessageQueueTriggerApiUpdate(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["mqTrigger"]
body, err := ioutil.ReadAll(r.Body)
if err != nil {
a.respondWithError(w, err)
return
}
var mqTrigger tpr.Messagequeuetrigger
err = json.Unmarshal(body, &mqTrigger)
if err != nil {
a.respondWithError(w, err)
return
}
if name != mqTrigger.Metadata.Name {
err = fission.MakeError(fission.ErrorInvalidArgument, "Message queue trigger name doesn't match URL")
a.respondWithError(w, err)
return
}
tnew, err := a.fissionClient.Messagequeuetriggers(mqTrigger.Metadata.Namespace).Update(&mqTrigger)
if err != nil {
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(tnew.Metadata)
if err != nil {
a.respondWithError(w, err)
return
}
a.respondWithSuccess(w, resp)
}
func (a *API) MessageQueueTriggerApiDelete(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["mqTrigger"]
ns := vars["namespace"]
if len(ns) == 0 {
ns = api.NamespaceDefault
}
err := a.fissionClient.Messagequeuetriggers(ns).Delete(name, &api.DeleteOptions{})
if err != nil {
a.respondWithError(w, err)
return
}
a.respondWithSuccess(w, []byte(""))
}
+170
View File
@@ -0,0 +1,170 @@
/*
Copyright 2016 The Fission Authors.
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 controller
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/gorilla/mux"
"k8s.io/client-go/1.5/pkg/api"
"github.com/fission/fission"
"github.com/fission/fission/tpr"
)
func (a *API) PackageApiList(w http.ResponseWriter, r *http.Request) {
funcs, err := a.fissionClient.Packages(api.NamespaceAll).List(api.ListOptions{})
if err != nil {
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(funcs.Items)
if err != nil {
a.respondWithError(w, err)
return
}
a.respondWithSuccess(w, resp)
}
func (a *API) PackageApiCreate(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
a.respondWithError(w, err)
return
}
var f tpr.Package
err = json.Unmarshal(body, &f)
if err != nil {
a.respondWithError(w, err)
return
}
err = validateResourceName(f.Metadata.Name)
if err != nil {
a.respondWithError(w, err)
return
}
// Ensure size limits
if len(f.Spec.Literal) > 256*1024 {
err := fission.MakeError(fission.ErrorInvalidArgument, "Package literal larger than 256K")
a.respondWithError(w, err)
return
}
fnew, err := a.fissionClient.Packages(f.Metadata.Namespace).Create(&f)
if err != nil {
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(fnew.Metadata)
if err != nil {
a.respondWithError(w, err)
return
}
w.WriteHeader(http.StatusCreated)
a.respondWithSuccess(w, resp)
}
func (a *API) PackageApiGet(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["package"]
ns := vars["namespace"]
if len(ns) == 0 {
ns = api.NamespaceDefault
}
raw := r.FormValue("raw") // just the deployment pkg
f, err := a.fissionClient.Packages(ns).Get(name)
if err != nil {
a.respondWithError(w, err)
return
}
var resp []byte
if raw != "" {
resp = []byte(f.Spec.Literal)
} else {
resp, err = json.Marshal(f)
if err != nil {
a.respondWithError(w, err)
return
}
}
a.respondWithSuccess(w, resp)
}
func (a *API) PackageApiUpdate(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["package"]
body, err := ioutil.ReadAll(r.Body)
if err != nil {
a.respondWithError(w, err)
return
}
var f tpr.Package
err = json.Unmarshal(body, &f)
if err != nil {
a.respondWithError(w, err)
return
}
if name != f.Metadata.Name {
err = fission.MakeError(fission.ErrorInvalidArgument, "Package name doesn't match URL")
a.respondWithError(w, err)
return
}
fnew, err := a.fissionClient.Packages(f.Metadata.Namespace).Update(&f)
if err != nil {
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(fnew.Metadata)
if err != nil {
a.respondWithError(w, err)
return
}
a.respondWithSuccess(w, resp)
}
func (a *API) PackageApiDelete(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["package"]
ns := vars["namespace"]
if len(ns) == 0 {
ns = api.NamespaceDefault
}
err := a.fissionClient.Packages(ns).Delete(name, &api.DeleteOptions{})
if err != nil {
a.respondWithError(w, err)
return
}
a.respondWithSuccess(w, []byte(""))
}
-287
View File
@@ -1,287 +0,0 @@
/*
Copyright 2016 The Fission Authors.
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 controller
import (
"errors"
"fmt"
"reflect"
"strings"
"time"
"github.com/coreos/etcd/client"
"github.com/satori/go.uuid"
log "github.com/sirupsen/logrus"
"golang.org/x/net/context"
"github.com/fission/fission"
)
type (
ResourceStore struct {
*FileStore
client.KeysAPI
serializer
}
)
func MakeResourceStore(fs *FileStore, etcdUrls []string) (*ResourceStore, error) {
ks, err := getEtcdKeyAPI(etcdUrls)
if err != nil {
return nil, err
}
s := JsonSerializer{}
return &ResourceStore{FileStore: fs, KeysAPI: ks, serializer: s}, nil
}
func getEtcdKeyAPI(etcdUrls []string) (client.KeysAPI, error) {
cfg := client.Config{
Endpoints: etcdUrls,
Transport: client.DefaultTransport,
// set timeout per request to fail fast when the target endpoint is unavailable
HeaderTimeoutPerRequest: time.Second,
}
c, err := client.New(cfg)
if err != nil {
log.Printf("failed to connect to etcd: %v", err)
return nil, err
}
return client.NewKeysAPI(c), nil
}
func getTypeName(r resource) (string, error) {
typ := reflect.TypeOf(r)
if typ.Kind().String() == "ptr" {
typ = typ.Elem()
}
typName := typ.Name()
if len(typName) == 0 {
return "", errors.New("Failed to get type")
}
return typName, nil
}
func getKey(r resource) (string, error) {
typName, err := getTypeName(r)
if err != nil {
return "", err
}
rkey := r.Key()
return (typName + "/" + rkey), nil
}
func (rs *ResourceStore) create(r resource) error {
key, err := getKey(r)
if err != nil {
return err
}
serialized, err := rs.serializer.serialize(r)
if err != nil {
return err
}
_, err = rs.KeysAPI.Set(context.Background(), key, string(serialized),
&client.SetOptions{PrevExist: client.PrevNoExist})
return handleEtcdErrorForResource(err, r)
}
func (rs *ResourceStore) read(rkey string, res resource) error {
typName, err := getTypeName(res)
if err != nil {
return err
}
key := typName + "/" + rkey
resp, err := rs.KeysAPI.Get(context.Background(), key, nil)
if err != nil {
return handleEtcdError(err, typName, rkey)
}
return rs.serializer.deserialize([]byte(resp.Node.Value), res)
}
func (rs *ResourceStore) update(r resource) error {
key, err := getKey(r)
if err != nil {
return err
}
serialized, err := rs.serializer.serialize(r)
if err != nil {
return err
}
_, err = rs.KeysAPI.Set(context.Background(), key, string(serialized),
&client.SetOptions{PrevExist: client.PrevExist})
return handleEtcdErrorForResource(err, r)
}
func (rs *ResourceStore) delete(typename, rkey string) error {
key := typename + "/" + rkey
_, err := rs.KeysAPI.Delete(context.Background(), key, nil) // ignore response
return handleEtcdError(err, typename, rkey)
}
// getAll finds all entries under key. If none or found or key
// doesn't exist, returns an empty slice.
func (rs *ResourceStore) getAll(key string) ([]string, error) {
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Recursive: true, Sort: true})
if err != nil {
if client.IsKeyNotFound(err) {
return []string{}, nil
}
return nil, handleEtcdError(err, "", key)
}
res := make([]string, 0, len(resp.Node.Nodes))
for _, n := range resp.Node.Nodes {
res = append(res, n.Value)
}
return res, nil
}
func (rs *ResourceStore) writeFile(parentKey string, contents []byte) (string, string, error) {
uid := uuid.NewV4().String()
err := rs.FileStore.write(uid, contents)
if err != nil {
return "", "", err
}
parentKey = "file/" + parentKey
resp, err := rs.KeysAPI.CreateInOrder(context.Background(), parentKey, uid, nil)
if err != nil {
_ = rs.FileStore.delete(uid)
return "", "", handleEtcdError(err, "file", parentKey)
}
return resp.Node.Key, uid, nil
}
func (rs *ResourceStore) readFile(key string, uid *string) ([]byte, error) {
key = "file/" + key
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Sort: true})
if err != nil {
return nil, handleEtcdError(err, "file", key)
}
if uid == nil {
// get latest
n := resp.Node.Nodes
uid = &n[len(n)-1].Value
} else {
// validate uid is in the list
found := false
for _, u := range resp.Node.Nodes {
if *uid == u.Value {
found = true
break
}
}
if !found {
return nil, errors.New("Invalid UID " + *uid)
}
}
contents, err := rs.FileStore.read(*uid)
return contents, err
}
func (rs *ResourceStore) deleteFile(key string, uid string) error {
key = "file/" + key
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Sort: true})
if err != nil {
return handleEtcdError(err, "file", key)
}
var node *client.Node
for _, u := range resp.Node.Nodes {
if u.Value == uid {
node = u
}
}
if node == nil {
log.WithFields(log.Fields{"key": key, "uid": uid}).Error("unreferenced file")
return errors.New("won't delete unreferenced file")
}
err = rs.FileStore.delete(node.Value)
if err != nil {
return err
}
_, err = rs.KeysAPI.Delete(context.Background(), node.Key, nil)
if err != nil {
return handleEtcdError(err, "", node.Key)
}
if len(resp.Node.Nodes) == 1 {
_, err = rs.KeysAPI.Delete(context.Background(), key, &client.DeleteOptions{Dir: true})
return handleEtcdError(err, "file", key)
}
return nil
}
func (rs *ResourceStore) deleteAllFiles(key string) error {
key = "file/" + key
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Sort: true})
if err != nil {
return handleEtcdError(err, "file", key)
}
for _, u := range resp.Node.Nodes {
err = rs.FileStore.delete(u.Value)
if err != nil {
return err
}
_, err = rs.KeysAPI.Delete(context.Background(), u.Key, nil)
if err != nil {
return handleEtcdError(err, "", u.Key)
}
}
_, err = rs.KeysAPI.Delete(context.Background(), key, &client.DeleteOptions{Dir: true})
return handleEtcdError(err, "file", key)
}
func handleEtcdErrorForResource(e error, r resource) error {
resourceType, _ := getTypeName(r)
return handleEtcdError(e, resourceType, r.Key())
}
func handleEtcdError(e error, resourceType string, resourceKey string) error {
ee, ok := e.(client.Error)
if !ok {
return e
}
code := fission.ErrorInternal
msg := ee.Error()
if len(resourceType) > 0 {
resourceType = strings.ToLower(resourceType) + " "
}
//TODO: handle any other etcd error codes we care about
switch ee.Code {
case client.ErrorCodeNodeExist:
code = fission.ErrorNameExists
msg = fmt.Sprintf("%s'%s' already exists", resourceType, resourceKey)
case client.ErrorCodeKeyNotFound:
code = fission.ErrorNotFound
msg = fmt.Sprintf("%s'%s' does not exist", resourceType, resourceKey)
}
return fission.MakeError(code, msg)
}
-152
View File
@@ -1,152 +0,0 @@
/*
Copyright 2016 The Fission Authors.
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 controller
import (
"io/ioutil"
"log"
"os"
"testing"
"github.com/coreos/etcd/client"
"golang.org/x/net/context"
)
type TestResource struct {
A string
B int
}
func (tr TestResource) Key() string {
return tr.A
}
func panicIf(err error) {
if err != nil {
log.Panicf("err: %v", err)
}
}
func assert(b bool, msg string) {
if !b {
log.Panic("assertion failed: " + msg)
}
}
func getTestResourceStore() (*FileStore, client.KeysAPI, *ResourceStore) {
// make a tmp dir
dir, err := ioutil.TempDir("", "testFileStore")
panicIf(err)
fs := MakeFileStore(dir)
rs, err := MakeResourceStore(fs, []string{"http://localhost:2379"})
panicIf(err)
return fs, rs.KeysAPI, rs
}
func TestResourceStore(t *testing.T) {
fs, ks, rs := getTestResourceStore()
defer os.RemoveAll(fs.root)
s := JsonSerializer{}
tr := TestResource{A: "hello", B: 1}
// Delete the key first, in case of a panic'd previous test run; ignore errors
_ = rs.delete("TestResource", tr.Key())
ks.Delete(context.Background(), "/TestResource", &client.DeleteOptions{Dir: true})
// Verify getAll for empty db
trs, err := rs.getAll("TestResource")
panicIf(err)
if len(trs) != 0 {
log.Fatalf("Expected zero length slice, got %v", trs)
}
// Create
err = rs.create(tr)
panicIf(err)
defer ks.Delete(context.Background(), "/TestResource", &client.DeleteOptions{Dir: true})
defer rs.delete("TestResource", tr.Key())
// Etcd key /TestResource/hello should exist
_, err = ks.Get(context.Background(), "TestResource/hello", nil)
panicIf(err)
// Read
tr1 := TestResource{}
err = rs.read(tr.Key(), &tr1)
panicIf(err)
assert(tr1 == tr, "retrieved value must equal created value")
// Update and Read
tr.B += 1
err = rs.update(tr)
panicIf(err)
err = rs.read(tr.Key(), &tr1)
panicIf(err)
assert(tr1 == tr, "retrieved value must equal updated value")
// Get list
results, err := rs.getAll("TestResource")
panicIf(err)
res := make([]TestResource, 0, 0)
for _, r := range results {
tmp := TestResource{}
err = s.deserialize([]byte(r), &tmp)
panicIf(err)
res = append(res, tmp)
}
assert(res[0] == tr, "value from retrieved list must equal updated value")
// file tests
fileKey := "ResourceStoreTest"
fileContents1 := []byte("hello")
fileContents2 := []byte("world")
key, uid1, err := rs.writeFile(fileKey, fileContents1)
panicIf(err)
defer rs.deleteFile(fileKey, uid1)
log.Printf("key = %v, uid = %v", key, uid1)
// read latest
contents, err := rs.readFile(fileKey, nil)
panicIf(err)
assert(string(contents) == string(fileContents1), "retrieved file contents must match written value")
// update-- same key new contents
_, uid2, err := rs.writeFile(fileKey, fileContents2)
panicIf(err)
defer rs.deleteFile(fileKey, uid2)
// read latest
contents, err = rs.readFile(fileKey, nil)
panicIf(err)
assert(string(contents) == string(fileContents2), "retrieved file contents must match updated value")
// read by uid
// 1
contents, err = rs.readFile(fileKey, &uid1)
panicIf(err)
assert(string(contents) == string(fileContents1), "retrieved file contents must match updated value")
// 2
contents, err = rs.readFile(fileKey, &uid2)
panicIf(err)
assert(string(contents) == string(fileContents2), "retrieved file contents must match updated value")
}
@@ -1,5 +1,5 @@
/*
Copyright 2016 The Fission Authors.
Copyright 2017 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,13 +16,23 @@ limitations under the License.
package controller
type (
resource interface {
Key() string
}
serializer interface {
serialize(r resource) ([]byte, error)
deserialize(buf []byte, r resource) error
}
import (
"fmt"
"log"
"net/http"
"net/http/httputil"
"net/url"
)
func (api *API) StorageServiceProxy(w http.ResponseWriter, r *http.Request) {
u := api.storageServiceUrl + "/v1/archive"
ssUrl, err := url.Parse(u)
if err != nil {
msg := fmt.Sprintf("Error parsing url %v: %v", u, err)
log.Println(msg)
http.Error(w, msg, 500)
return
}
proxy := httputil.NewSingleHostReverseProxy(ssUrl)
proxy.ServeHTTP(w, r)
}
+52 -61
View File
@@ -23,161 +23,152 @@ import (
"github.com/gorilla/mux"
"github.com/robfig/cron"
log "github.com/sirupsen/logrus"
"k8s.io/client-go/1.5/pkg/api"
"github.com/fission/fission"
"github.com/fission/fission/tpr"
)
func (api *API) TimeTriggerApiList(w http.ResponseWriter, r *http.Request) {
triggers, err := api.TimeTriggerStore.List()
func (a *API) TimeTriggerApiList(w http.ResponseWriter, r *http.Request) {
triggers, err := a.fissionClient.Timetriggers(api.NamespaceAll).List(api.ListOptions{})
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(triggers)
resp, err := json.Marshal(triggers.Items)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
a.respondWithSuccess(w, resp)
}
func (api *API) TimeTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
func (a *API) TimeTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
var t fission.TimeTrigger
var t tpr.Timetrigger
err = json.Unmarshal(body, &t)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
triggers, err := api.TimeTriggerStore.List()
err = validateResourceName(t.Metadata.Name)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
for _, trigger := range triggers {
if trigger.Name == t.Name {
err = fission.MakeError(fission.ErrorNameExists,
"TimeTrigger with same name already exists")
api.respondWithError(w, err)
return
}
}
_, err = cron.Parse(t.Cron)
// validate
_, err = cron.Parse(t.Spec.Cron)
if err != nil {
err = fission.MakeError(fission.ErrorInvalidArgument, "TimeTrigger cron spec is not valid")
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
uid, err := api.TimeTriggerStore.Create(&t)
tnew, err := a.fissionClient.Timetriggers(t.Metadata.Namespace).Create(&t)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
m := &fission.Metadata{Name: t.Metadata.Name, Uid: uid}
resp, err := json.Marshal(m)
resp, err := json.Marshal(tnew.Metadata)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
w.WriteHeader(http.StatusCreated)
api.respondWithSuccess(w, resp)
a.respondWithSuccess(w, resp)
}
func (api *API) TimeTriggerApiGet(w http.ResponseWriter, r *http.Request) {
var m fission.Metadata
func (a *API) TimeTriggerApiGet(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
m.Name = vars["timeTrigger"]
m.Uid = r.FormValue("uid") // empty if uid is absent
name := vars["timeTrigger"]
ns := vars["namespace"]
if len(ns) == 0 {
ns = api.NamespaceDefault
}
t, err := api.TimeTriggerStore.Get(&m)
t, err := a.fissionClient.Timetriggers(ns).Get(name)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(t)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
a.respondWithSuccess(w, resp)
}
func (api *API) TimeTriggerApiUpdate(w http.ResponseWriter, r *http.Request) {
func (a *API) TimeTriggerApiUpdate(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["timeTrigger"]
body, err := ioutil.ReadAll(r.Body)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
var t fission.TimeTrigger
var t tpr.Timetrigger
err = json.Unmarshal(body, &t)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
if name != t.Metadata.Name {
err = fission.MakeError(fission.ErrorInvalidArgument, "TimeTrigger name doesn't match URL")
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
_, err = cron.Parse(t.Cron)
_, err = cron.Parse(t.Spec.Cron)
if err != nil {
err = fission.MakeError(fission.ErrorInvalidArgument, "TimeTrigger cron spec is not valid")
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
uid, err := api.TimeTriggerStore.Update(&t)
tnew, err := a.fissionClient.Timetriggers(t.Metadata.Namespace).Update(&t)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
m := &fission.Metadata{Name: t.Metadata.Name, Uid: uid}
resp, err := json.Marshal(m)
resp, err := json.Marshal(tnew.Metadata)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
a.respondWithSuccess(w, resp)
}
func (api *API) TimeTriggerApiDelete(w http.ResponseWriter, r *http.Request) {
func (a *API) TimeTriggerApiDelete(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
var m fission.Metadata
m.Name = vars["timeTrigger"]
m.Uid = r.FormValue("uid") // empty if uid is absent
if len(m.Uid) == 0 {
log.WithFields(log.Fields{"timeTrigger": m.Name}).Info("Deleting all versions")
name := vars["timeTrigger"]
ns := vars["namespace"]
if len(ns) == 0 {
ns = api.NamespaceDefault
}
err := api.TimeTriggerStore.Delete(m)
err := a.fissionClient.Timetriggers(ns).Delete(name, &api.DeleteOptions{})
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
api.respondWithSuccess(w, []byte(""))
a.respondWithSuccess(w, []byte(""))
}
-79
View File
@@ -1,79 +0,0 @@
/*
Copyrigtt 2017 The Fission Authors.
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
tttp://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 controller
import (
"github.com/satori/go.uuid"
"github.com/fission/fission"
)
type TimeTriggerStore struct {
ResourceStore
}
func (tts *TimeTriggerStore) Create(tt *fission.TimeTrigger) (string, error) {
tt.Metadata.Uid = uuid.NewV4().String()
return tt.Metadata.Uid, tts.ResourceStore.create(tt)
}
func (tts *TimeTriggerStore) Get(m *fission.Metadata) (*fission.TimeTrigger, error) {
var tt fission.TimeTrigger
err := tts.ResourceStore.read(m.Name, &tt)
if err != nil {
return nil, err
}
return &tt, nil
}
func (tts *TimeTriggerStore) Update(tt *fission.TimeTrigger) (string, error) {
tt.Metadata.Uid = uuid.NewV4().String()
return tt.Metadata.Uid, tts.ResourceStore.update(tt)
}
func (tts *TimeTriggerStore) Delete(m fission.Metadata) error {
typeName, err := getTypeName(fission.TimeTrigger{})
if err != nil {
return err
}
return tts.ResourceStore.delete(typeName, m.Name)
}
func (tts *TimeTriggerStore) List() ([]fission.TimeTrigger, error) {
typeName, err := getTypeName(fission.TimeTrigger{})
if err != nil {
return nil, err
}
bufs, err := tts.ResourceStore.getAll(typeName)
if err != nil {
return nil, err
}
triggers := make([]fission.TimeTrigger, 0, len(bufs))
js := JsonSerializer{}
for _, buf := range bufs {
var tt fission.TimeTrigger
err = js.deserialize([]byte(buf), &tt)
if err != nil {
return nil, err
}
triggers = append(triggers, tt)
}
return triggers, nil
}
+40
View File
@@ -0,0 +1,40 @@
/*
Copyright 2016 The Fission Authors.
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 controller
import (
"errors"
"regexp"
"github.com/fission/fission/tpr"
)
func makeTPRBackedAPI() (*API, error) {
fissionClient, _, err := tpr.MakeFissionClient()
if err != nil {
return nil, err
}
return &API{fissionClient: fissionClient}, nil
}
func validateResourceName(name string) error {
re := regexp.MustCompile(`[a-z0-9]([-a-z0-9]*[a-z0-9])?`)
if len(re.FindString(name)) != len(name) {
return errors.New("Name must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?'")
}
return nil
}
+45 -40
View File
@@ -22,102 +22,107 @@ import (
"net/http"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"k8s.io/client-go/1.5/pkg/api"
"github.com/fission/fission"
"github.com/fission/fission/tpr"
)
func (api *API) WatchApiList(w http.ResponseWriter, r *http.Request) {
watches, err := api.WatchStore.List()
func (a *API) WatchApiList(w http.ResponseWriter, r *http.Request) {
watches, err := a.fissionClient.Kuberneteswatchtriggers(api.NamespaceAll).List(api.ListOptions{})
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(watches)
resp, err := json.Marshal(watches.Items)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
a.respondWithSuccess(w, resp)
}
func (api *API) WatchApiCreate(w http.ResponseWriter, r *http.Request) {
func (a *API) WatchApiCreate(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
var watch fission.Watch
var watch tpr.Kuberneteswatchtrigger
err = json.Unmarshal(body, &watch)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
watch.Target = fission.UrlForFunction(&watch.Function)
uid, err := api.WatchStore.Create(&watch)
err = validateResourceName(watch.Metadata.Name)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
m := &fission.Metadata{Name: watch.Metadata.Name, Uid: uid}
resp, err := json.Marshal(m)
// TODO check for duplicate watches
wnew, err := a.fissionClient.Kuberneteswatchtriggers(watch.Metadata.Namespace).Create(&watch)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(wnew.Metadata)
if err != nil {
a.respondWithError(w, err)
return
}
w.WriteHeader(http.StatusCreated)
api.respondWithSuccess(w, resp)
a.respondWithSuccess(w, resp)
}
func (api *API) WatchApiGet(w http.ResponseWriter, r *http.Request) {
var m fission.Metadata
func (a *API) WatchApiGet(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
m.Name = vars["watch"]
m.Uid = r.FormValue("uid") // empty if uid is absent
name := vars["watch"]
ns := vars["namespace"]
if len(ns) == 0 {
ns = api.NamespaceDefault
}
watch, err := api.WatchStore.Get(&m)
watch, err := a.fissionClient.Kuberneteswatchtriggers(ns).Get(name)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(watch)
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
a.respondWithSuccess(w, resp)
}
func (api *API) WatchApiUpdate(w http.ResponseWriter, r *http.Request) {
api.respondWithError(w, fission.MakeError(fission.ErrorNotImplmented,
func (a *API) WatchApiUpdate(w http.ResponseWriter, r *http.Request) {
a.respondWithError(w, fission.MakeError(fission.ErrorNotImplmented,
"Not implemented"))
}
func (api *API) WatchApiDelete(w http.ResponseWriter, r *http.Request) {
func (a *API) WatchApiDelete(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
var m fission.Metadata
m.Name = vars["watch"]
m.Uid = r.FormValue("uid") // empty if uid is absent
if len(m.Uid) == 0 {
log.WithFields(log.Fields{"watch": m.Name}).Info("Deleting all versions")
name := vars["watch"]
ns := vars["namespace"]
if len(ns) == 0 {
ns = api.NamespaceDefault
}
err := api.WatchStore.Delete(m)
err := a.fissionClient.Kuberneteswatchtriggers(ns).Delete(name, &api.DeleteOptions{})
if err != nil {
api.respondWithError(w, err)
a.respondWithError(w, err)
return
}
api.respondWithSuccess(w, []byte(""))
a.respondWithSuccess(w, []byte(""))
}
-79
View File
@@ -1,79 +0,0 @@
/*
Copyright 2016 The Fission Authors.
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 controller
import (
"github.com/satori/go.uuid"
"github.com/fission/fission"
)
type WatchStore struct {
ResourceStore
}
func (ws *WatchStore) Create(w *fission.Watch) (string, error) {
w.Metadata.Uid = uuid.NewV4().String()
return w.Metadata.Uid, ws.ResourceStore.create(w)
}
func (ws *WatchStore) Get(m *fission.Metadata) (*fission.Watch, error) {
var w fission.Watch
err := ws.ResourceStore.read(m.Name, &w)
if err != nil {
return nil, err
}
return &w, err
}
func (ws *WatchStore) Update(w *fission.Watch) (string, error) {
w.Metadata.Uid = uuid.NewV4().String()
return w.Metadata.Uid, ws.ResourceStore.update(w)
}
func (ws *WatchStore) Delete(m fission.Metadata) error {
typeName, err := getTypeName(fission.Watch{})
if err != nil {
return err
}
return ws.ResourceStore.delete(typeName, m.Name)
}
func (ws *WatchStore) List() ([]fission.Watch, error) {
typeName, err := getTypeName(fission.Watch{})
if err != nil {
return nil, err
}
bufs, err := ws.ResourceStore.getAll(typeName)
if err != nil {
return nil, err
}
watches := make([]fission.Watch, 0, len(bufs))
js := JsonSerializer{}
for _, buf := range bufs {
var w fission.Watch
err = js.deserialize([]byte(buf), &w)
if err != nil {
return nil, err
}
watches = append(watches, w)
}
return watches, nil
}
+16
View File
@@ -0,0 +1,16 @@
#
# Handles POST /guestbook -- adds item to guestbook
#
from flask import request, redirect
import redis
# Connect to redis.
redisConnection = redis.StrictRedis(host='redis.guestbook', port=6379, db=0)
def main():
# Read the item from POST params, add it to redis, and redirect
# back to the list
item = request.form['text']
redisConnection.rpush('guestbook', item)
return redirect('/guestbook', code=303)
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
kubectl delete ns guestbook
fission fn delete --name guestbook-add
fission fn delete --name guestbook-get
fission route list|grep guestbook|cut -f1 -d' '|xargs -n1 fission route delete --name
+28
View File
@@ -0,0 +1,28 @@
#
# Handles GET /guestbook -- returns a list of items in the guestbook
# with a form to add more.
#
from flask import current_app, escape
import redis
# Connect to redis. This is run only when this file is loaded; as
# long as the pod is alive, the connection is reused.
redisConnection = redis.StrictRedis(host='redis.guestbook', port=6379, db=0)
def main():
messages = redisConnection.lrange('guestbook', 0, -1)
items = [("<li>%s</li>" % escape(m.decode('utf-8'))) for m in messages]
ul = "<ul>%s</ul>" % "\n".join(items)
return """
<html><body style="font-family:sans-serif;font-size:2rem;padding:40px">
<h1>Guestbook</h1>
<form action="/guestbook" method="POST">
<input type="text" name="text">
<button type="submit">Add</button>
</form>
<hr/>
%s
</body></html>
""" % ul
+45
View File
@@ -0,0 +1,45 @@
apiVersion: v1
kind: Namespace
metadata:
name: guestbook
labels:
name: guestbook
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
run: redis
name: redis
namespace: guestbook
spec:
replicas: 1
selector:
matchLabels:
run: redis
template:
metadata:
labels:
run: redis
spec:
containers:
- image: redis
name: redis
---
apiVersion: v1
kind: Service
metadata:
labels:
run: redis
name: redis
namespace: guestbook
spec:
selector:
run: redis
type: ClusterIP
ports:
- port: 6379
protocol: TCP
targetPort: 6379
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
DEMO_RUN_FAST=1
ROOT_DIR=$(dirname $0)/..
. $ROOT_DIR/util.sh
desc "Deploy redis"
run "kubectl create -f redis.yaml"
desc "Ensure fission has a python environment"
run "fission env create --name python --image fission/python-env"
desc "Register fission functions"
run "fission function create --name guestbook-get --env python --code get.py --url /guestbook --method GET"
run "fission function create --name guestbook-add --env python --code add.py --url /guestbook --method POST"
echo "http://$FISSION_ROUTER/guestbook"
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
fission fn delete --name hello
fission route delete --name $(fission route list|grep hello|cut -f1 -d' ')
+7
View File
@@ -0,0 +1,7 @@
module.exports = async function(context) {
return {
status: 200,
body: "Hello, world!\n"
};
}
+40
View File
@@ -0,0 +1,40 @@
#!/bin/bash
DEMO_RUN_FAST=1
ROOT_DIR=$(dirname $0)/..
. $ROOT_DIR/util.sh
desc "Kubernetes cluster"
run "kubectl get nodes"
desc "Fission installed"
run "kubectl --namespace fission get deployment"
clear
desc "Hello world function"
run "cat hello.js"
desc "Add NodeJS environment to fission"
run "fission env create --name nodejs --image fission/node-env"
desc "NodeJS environment pods"
run "kubectl --namespace fission-function get pod -l environmentName=nodejs"
desc "Upload a function to fission"
run "fission function create --name hello --env nodejs --code hello.js"
desc "Set up a route \(HTTP trigger\) for the function"
run "fission route create --method GET --url /hello --function hello"
sleep 2
desc "Finally, run the function"
run "time -p curl http://$FISSION_ROUTER/hello"
desc "Run the function again"
run "time -p curl http://$FISSION_ROUTER/hello"
run "time -p curl http://$FISSION_ROUTER/hello"
desc "The pod is now labeled by the function name"
run "kubectl --namespace fission-function get pod -l functionName=hello"
+78
View File
@@ -0,0 +1,78 @@
#!/bin/bash
# Copyright 2016 The Kubernetes Authors.
#
# 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.
readonly reset=$(tput sgr0)
readonly green=$(tput bold; tput setaf 2)
readonly yellow=$(tput bold; tput setaf 3)
readonly blue=$(tput bold; tput setaf 6)
readonly timeout=$(if [ "$(uname)" == "Darwin" ]; then echo "1"; else echo "0.1"; fi)
function desc() {
maybe_first_prompt
echo "$blue# $@$reset"
prompt
}
function prompt() {
echo -n "$yellow\$ $reset"
}
started=""
function maybe_first_prompt() {
if [ -z "$started" ]; then
prompt
started=true
fi
}
# After a `run` this variable will hold the stdout of the command that was run.
# If the command was interactive, this will likely be garbage.
#DEMO_RUN_STDOUT=""
function run() {
maybe_first_prompt
rate=25
if [ -n "$DEMO_RUN_FAST" ]; then
rate=1000
fi
echo "$green$1$reset" | pv -qL $rate
if [ -n "$DEMO_RUN_FAST" ]; then
sleep 0.5
fi
$1
echo; echo -n ">"
read -s
echo -e "\b "
# OFILE="$(mktemp -t $(basename $0).XXXXXX)"
# script -q -t0 "$OFILE" $1 # [removed -e,-c options -soam]
# r=$?
# read -d '' -t "${timeout}" -n 10000 # clear stdin
# prompt
# if [ -z "$DEMO_AUTO_RUN" ]; then
# read -s
# fi
# DEMO_RUN_STDOUT="$(tail -n +2 $OFILE | sed 's/\r//g')"
# return $r
}
function relative() {
for arg; do
echo "$(realpath $(dirname $(which $0)))/$arg" | sed "s|$(realpath $(pwd))|.|"
done
}
SSH_NODE=$(kubectl get nodes | tail -1 | cut -f1 -d' ')
#trap "echo" EXIT
+10
View File
@@ -0,0 +1,10 @@
FROM alpine:3.5
RUN apk update
RUN apk add coreutils binutils findutils grep
COPY . /app
WORKDIR /app
EXPOSE 8888
ENTRYPOINT ["./server"]
+50
View File
@@ -0,0 +1,50 @@
# Binary Environment Examples
The `binary` runtime is a go server that uses a subprocess to invoke executables or execute shell scripts.
Use Cases
- Execute bash scripts
- Run executables of languages that have no dedicated environment yet.
## Words of Caution
The environment runs on an alpine image with some additional utilty commandline tools installed, such as 'grep'.
However, in case you want to make use of more ecsoteric commandline tools, you should add the relevant apk to the
Dockerfile and build a new binary environment. See 'Compiling' for instructions.
When executing functions using binaries, **ensure that the executable is built for the right architecture**.
Using the default binary environment this means that the binary should be build for Linux.
## Usage
The interface to the executable used by this environment is somewhat similar to a [CGI interface](https://en.wikipedia.org/wiki/Common_Gateway_Interface).
This means that any HTTP headers are converted to environment variables of the form "HTTP_<header-name>". For example these
are some of frequently occurring headers:
```
# Request Metadata
CONTENT_LENGTH
REQUEST_URI
REQUEST_METHOD
# HTTP Headers
HTTP_ACCEPT
HTTP_USER-AGENT
...
```
The body of HTTP piped over the STDIN to the executable.
All output that is provided to the server over the STDOUT will be transformed into the HTTP response.
## Compiling
In order to build the Dockerfile, the server needs to be compiled to the right architecture.
```bash
sh ./build.sh
```
Build the Dockerfile:
```bash
docker build --tag=${USER}/binary-env .
```
See the [README](../../examples/binary/README.md) in the binary examples directory for usage instructions.
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
GOOS=linux GOARCH=386 go build -o server .
+47
View File
@@ -0,0 +1,47 @@
package main
import (
"fmt"
"strings"
)
// Utility functions for working with environment variables
type Env struct {
Vars []*EnvVar
}
type EnvVar struct {
Key string
Val string
}
func FromString(rawEnvVar string) *EnvVar {
parts := strings.SplitN(rawEnvVar, "=", 2)
return &EnvVar{parts[0], parts[1]}
}
func (ev *EnvVar) ToString() string {
return fmt.Sprintf("%s=%s", ev.Key, ev.Val)
}
func (e *Env) SetEnv(envVar *EnvVar) {
e.Vars = append(e.Vars, envVar)
}
func (e *Env) ToStringEnv() []string {
var result []string
for _, envVar := range e.Vars {
result = append(result, envVar.ToString())
}
return result
}
func NewEnv(stringEnv []string) *Env {
env := &Env{}
if stringEnv != nil {
for _, rawEnvVar := range stringEnv {
env.SetEnv(FromString(rawEnvVar))
}
}
return env
}
+134
View File
@@ -0,0 +1,134 @@
package main
import (
"flag"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
)
const (
DEFAULT_CODE_PATH = "/userfunc/user"
DEFAULT_INTERNAL_CODE_PATH = "/bin/userfunc"
)
var specialized bool
type BinaryServer struct {
fetchedCodePath string
internalCodePath string
}
func (bs *BinaryServer) SpecializeHandler(w http.ResponseWriter, r *http.Request) {
if specialized {
w.WriteHeader(400)
w.Write([]byte("Not a generic container"))
return
}
_, err := os.Stat(bs.fetchedCodePath)
if err != nil {
if os.IsNotExist(err) {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte(bs.fetchedCodePath + ": not found"))
return
} else {
panic(err)
}
}
// Future: Check if executable is correct architecture/executable.
// Copy the executable to ensure that file is executable and immutable.
userFunc, err := ioutil.ReadFile(bs.fetchedCodePath)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Failed to read executable."))
return
}
err = ioutil.WriteFile(bs.internalCodePath, userFunc, 0555)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Failed to write executable to target location."))
return
}
fmt.Println("Specializing ...")
specialized = true
fmt.Println("Done")
}
func (bs *BinaryServer) InvocationHandler(w http.ResponseWriter, r *http.Request) {
if !specialized {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Generic container: no requests supported"))
return
}
// CGI-like passing of environment variables
execEnv := NewEnv(nil)
execEnv.SetEnv(&EnvVar{"REQUEST_METHOD", r.Method})
execEnv.SetEnv(&EnvVar{"REQUEST_URI", r.RequestURI})
execEnv.SetEnv(&EnvVar{"CONTENT_LENGTH", fmt.Sprintf("%d", r.ContentLength)})
for header, val := range r.Header {
execEnv.SetEnv(&EnvVar{fmt.Sprintf("HTTP_%s", strings.ToUpper(header)), val[0]})
}
// Future: could be improved by keeping subprocess open while environment is specialized
cmd := exec.Command(bs.internalCodePath)
cmd.Env = execEnv.ToStringEnv()
if r.ContentLength != 0 {
fmt.Println(r.ContentLength)
stdin, err := cmd.StdinPipe()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("Failed to get STDIN pipe: %s", err)))
panic(err)
}
_, err = io.Copy(stdin, r.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("Failed to pipe input: %s", err)))
}
stdin.Close()
}
out, err := cmd.Output()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("Function error: %s", err)))
return
}
w.WriteHeader(http.StatusOK)
w.Write(out)
}
func main() {
codePath := flag.String("c", DEFAULT_CODE_PATH, "Path to expected fetched executable.")
internalCodePath := flag.String("i", DEFAULT_INTERNAL_CODE_PATH, "Path to specialized executable.")
flag.Parse()
absInternalCodePath, err := filepath.Abs(*internalCodePath)
if err != nil {
panic(err)
}
fmt.Printf("Using fetched code path: %s\n", *codePath)
fmt.Printf("Using internal code path: %s\n", absInternalCodePath)
server := &BinaryServer{*codePath, absInternalCodePath}
http.HandleFunc("/", server.InvocationHandler)
http.HandleFunc("/specialize", server.SpecializeHandler)
fmt.Println("Listening on 8888 ...")
err = http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}
-1
View File
@@ -1 +0,0 @@
GOOS=linux GOARCH=386 go build -o fetcher .
+35
View File
@@ -0,0 +1,35 @@
package client
import (
"bytes"
"encoding/json"
"net/http"
//"time"
"github.com/fission/fission"
"github.com/fission/fission/environments/fetcher"
//"github.com/fission/fission/router"
)
func DoFetchRequest(fetcherUrl string, fr *fetcher.FetchRequest) error {
body, err := json.Marshal(fr)
if err != nil {
return err
}
// client := http.Client{
// Transport: router.MakeRetryingRoundTripper(10, 50*time.Millisecond),
// }
resp, err := http.Post(fetcherUrl, "application/json", bytes.NewReader(body))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return fission.MakeErrorFromHTTP(resp)
}
return nil
}
+2
View File
@@ -0,0 +1,2 @@
#!/bin/sh
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o fetcher .
+26
View File
@@ -0,0 +1,26 @@
package main
import (
"log"
"net/http"
"os"
"github.com/fission/fission/environments/fetcher"
)
// Usage: fetcher <shared volume path>
func main() {
dir := os.Args[1]
if _, err := os.Stat(dir); err != nil {
if os.IsNotExist(err) {
err = os.MkdirAll(dir, os.ModeDir|0700)
if err != nil {
log.Fatalf("Error creating directory: %v", err)
}
}
}
fetcher := fetcher.MakeFetcher(dir)
mux := http.NewServeMux()
mux.HandleFunc("/", fetcher.Handler)
http.ListenAndServe(":8000", mux)
}
+207 -63
View File
@@ -1,34 +1,120 @@
package main
package fetcher
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"time"
"github.com/mholt/archiver"
"github.com/satori/go.uuid"
"k8s.io/client-go/1.5/kubernetes"
"k8s.io/client-go/1.5/pkg/api"
"github.com/fission/fission"
"github.com/fission/fission/tpr"
)
type FetchRequest struct {
Url string `json:"url"`
Filename string `json:"filename"`
}
type (
FetchRequestType int
type Fetcher struct {
sharedVolumePath string
}
FetchRequest struct {
FetchType FetchRequestType `json:"fetchType"`
Function api.ObjectMeta `json:"function"`
Url string `json:"url"`
StorageSvcUrl string `json:"storagesvcurl"`
Filename string `json:"filename"`
}
Fetcher struct {
sharedVolumePath string
fissionClient *tpr.FissionClient
kubeClient *kubernetes.Clientset
}
)
const (
FETCH_SOURCE = iota
FETCH_DEPLOYMENT
FETCH_URL // remove this?
)
func MakeFetcher(sharedVolumePath string) *Fetcher {
return &Fetcher{sharedVolumePath: sharedVolumePath}
fissionClient, kubeClient, err := tpr.MakeFissionClient()
if err != nil {
return nil
}
return &Fetcher{
sharedVolumePath: sharedVolumePath,
fissionClient: fissionClient,
kubeClient: kubeClient,
}
}
func (fetcher *Fetcher) handler(w http.ResponseWriter, r *http.Request) {
func downloadUrl(url string, localPath string) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
err = ioutil.WriteFile(localPath, body, 0600)
if err != nil {
return err
}
return nil
}
func verifyChecksum(path string, checksum *fission.Checksum) error {
if checksum.Type != fission.ChecksumTypeSHA256 {
return fission.MakeError(fission.ErrorInvalidArgument, "Unsupported checksum type")
}
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
hasher := sha256.New()
_, err = io.Copy(hasher, f)
if err != nil {
return err
}
c := hex.EncodeToString(hasher.Sum(nil))
if c != checksum.Sum {
return fission.MakeError(fission.ErrorChecksumFail, "Checksum validation failed")
}
return nil
}
func (fetcher *Fetcher) Handler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "", 404)
http.Error(w, "", 405)
return
}
startTime := time.Now()
defer func() {
elapsed := time.Now().Sub(startTime)
log.Printf("elapsed time in fetch request = %v", elapsed)
}()
// parse request
body, err := ioutil.ReadAll(r.Body)
if err != nil {
@@ -40,65 +126,123 @@ func (fetcher *Fetcher) handler(w http.ResponseWriter, r *http.Request) {
err = json.Unmarshal(body, &req)
if err != nil {
log.Printf("Error reading request body: %v", err)
http.Error(w, err.Error(), 400)
return
}
log.Printf("fetcher received request: %v", req)
tmpFile := req.Filename + ".tmp"
tmpPath := filepath.Join(fetcher.sharedVolumePath, tmpFile)
if req.FetchType == FETCH_URL {
// fetch the file and save it to the tmp path
err := downloadUrl(req.Url, tmpPath)
if err != nil {
e := fmt.Sprintf("Failed to download url %v: %v", req.Url, err)
log.Printf(e)
http.Error(w, e, 400)
return
}
} else {
// get function object
fn, err := fetcher.fissionClient.Functions(req.Function.Namespace).Get(req.Function.Name)
if err != nil {
e := fmt.Sprintf("Failed to get function: %v", err)
log.Printf(e)
http.Error(w, e, 500)
return
}
// get pkg
var pkg *tpr.Package
if req.FetchType == FETCH_SOURCE {
pkg, err = fetcher.fissionClient.Packages(fn.Spec.Source.PackageRef.Namespace).Get(fn.Spec.Source.PackageRef.Name)
} else if req.FetchType == FETCH_DEPLOYMENT {
pkg, err = fetcher.fissionClient.Packages(fn.Spec.Deployment.PackageRef.Namespace).Get(fn.Spec.Deployment.PackageRef.Name)
}
if err != nil {
e := fmt.Sprintf("Failed to get package: %v", err)
log.Printf(e)
http.Error(w, e, 500)
return
}
// get package data as literal or by url
if len(pkg.Spec.Literal) > 0 {
// write pkg.Literal into tmpPath
err = ioutil.WriteFile(tmpPath, pkg.Spec.Literal, 0600)
if err != nil {
e := fmt.Sprintf("Failed to write file %v: %v", tmpPath, err)
log.Printf(e)
http.Error(w, e, 500)
return
}
} else {
// download and verify
err = downloadUrl(pkg.Spec.URL, tmpPath)
if err != nil {
e := fmt.Sprintf("Failed to download url %v: %v", req.Url, err)
log.Printf(e)
http.Error(w, e, 400)
return
}
err = verifyChecksum(tmpPath, &pkg.Spec.Checksum)
if err != nil {
e := fmt.Sprintf("Failed to verify checksum: %v", err)
log.Printf(e)
http.Error(w, e, 400)
return
}
}
}
// check file type here, if the file is a zip file unarchive it.
if archiver.Zip.Match(tmpPath) {
// unarchive tmp file to a tmp unarchive path
tmpUnarchivePath := filepath.Join(fetcher.sharedVolumePath, uuid.NewV4().String())
err = fetcher.unarchive(tmpPath, tmpUnarchivePath)
if err != nil {
log.Println(err.Error())
http.Error(w, err.Error(), 500)
return
}
tmpPath = tmpUnarchivePath
}
// move tmp file to requested filename
err = fetcher.rename(tmpPath, filepath.Join(fetcher.sharedVolumePath, req.Filename))
if err != nil {
log.Println(err.Error())
http.Error(w, err.Error(), 500)
return
}
log.Printf("fetcher request: %v", req)
// fetch the file and save it to tmp path
resp, err := http.Get(req.Url)
if err != nil {
e := fmt.Sprintf("Failed to fetch from url: %v", err)
log.Printf(e)
http.Error(w, e, 400)
return
}
defer resp.Body.Close()
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
e := fmt.Sprintf("Failed to read from url: %v", err)
log.Printf(e)
http.Error(w, e, 400)
return
}
tmpFile := req.Filename + ".tmp"
tmpPath := filepath.Join(fetcher.sharedVolumePath, tmpFile)
err = ioutil.WriteFile(tmpPath, body, 0600)
if err != nil {
e := fmt.Sprintf("Failed to write file: %v", err)
log.Printf(e)
http.Error(w, e, 500)
return
}
// TODO: add signature verification
// move tmp file to requested filename
err = os.Rename(tmpPath, filepath.Join(fetcher.sharedVolumePath, req.Filename))
if err != nil {
e := fmt.Sprintf("Failed to move file: %v", err)
log.Printf(e)
http.Error(w, e, 500)
return
}
log.Printf("Completed fetch request")
// all done
w.WriteHeader(http.StatusOK)
}
// Usage: fetcher <shared volume path>
func main() {
dir := os.Args[1]
if _, err := os.Stat(dir); err != nil {
if os.IsNotExist(err) {
err = os.MkdirAll(dir, os.ModeDir|0700)
if err != nil {
log.Fatalf("Error creating directory: %v", err)
}
}
func (fetcher *Fetcher) rename(src string, dst string) error {
err := os.Rename(src, dst)
if err != nil {
return errors.New(fmt.Sprintf("Failed to move file: %v", err))
}
fetcher := MakeFetcher(dir)
mux := http.NewServeMux()
mux.HandleFunc("/", fetcher.handler)
http.ListenAndServe(":8000", mux)
return nil
}
// archive is a function that zips directory into a zip file
func (fetcher *Fetcher) archive(src string, dst string) error {
return archiver.Zip.Make(dst, []string{src})
}
// unarchive is a function that unzips a zip file to destination
func (fetcher *Fetcher) unarchive(src string, dst string) error {
err := archiver.Zip.Open(src, dst)
if err != nil {
return errors.New(fmt.Sprintf("Failed to unzip file: %v", err))
}
return nil
}
+11
View File
@@ -0,0 +1,11 @@
FROM alpine:3.5
RUN apk update
RUN apk add --no-cache python3 python3-dev build-base
RUN pip3 install --upgrade pip
RUN rm -r /root/.cache
ADD defaultBuildCmd /usr/local/bin/build
ADD builder /
EXPOSE 8000
+11
View File
@@ -0,0 +1,11 @@
#!/bin/sh
set -e
builderDir=${GOPATH}/src/github.com/fission/fission/builder/cmd
pushd ${builderDir}
GOOS=linux GOARCH=386 go build -o builder .
popd
cp ${builderDir}/builder .
docker build -t python-builder .
docker tag python-builder fission/python-builder:$tag
docker push fission/python-builder:$tag
+2
View File
@@ -0,0 +1,2 @@
#!/bin/sh
pip3 install -r ${SRC_PKG}/requirements.txt -t ${SRC_PKG} && cp -r ${SRC_PKG} ${DEPLOY_PKG}
+18 -3
View File
@@ -2,9 +2,20 @@
This is the Ruby environment for Fission.
It's a Docker image containing a Ruby 2.4.1 runtime, along with a
dynamic loader. A few common dependencies are included in the
Gemfile.
It's a Docker image containing a Ruby 2.4.1 runtime. The image uses
Rack with WEBrick to host the internal web server.
The environment works via convention where you create a Ruby method
called `handler` with a single optional argument, a `Fission::Context`
object.
The `Fission::Context` object gives access to the Rack env, a
request object, and a logger. Please see `fission/context.rb` for the
public api.
The `Fission::Request` object is a subclass of `Rack::Request` and
provides access to parameters and headers. See `fission/request.rb`
for the public api.
## Customizing this image
@@ -39,3 +50,7 @@ Or, if you already have an environment, you can update its image:
After this, fission functions that have the env parameter set to the
same environment name as this command will use this environment.
## Creating functions to use this image
See the [examples README](examples/ruby/README.md).
+22
View File
@@ -0,0 +1,22 @@
# frozen_string_literal: true
require_relative 'request'
require 'forwardable'
module Fission
class Context
extend Forwardable
def_instance_delegator :request, :logger
attr_reader :env
def initialize(env)
@env = env
end
def request
@request ||= Request.new(env)
end
end
end
+23
View File
@@ -0,0 +1,23 @@
# frozen_string_literal: true
require_relative 'context'
module Fission
module Handler
def self.call(env)
context = Context.new(env)
response = if method(:handler).arity > 0
handler(context)
else
handler
end
response.is_a?(Array) ? response : Rack::Response.new([response]).finish
rescue => e
context.logger.error(%(Function failed - #{e}\n#{e.backtrace.join("\n")}))
Rack::Response.new(['500 Internal Server Error'], 500, {}).finish
end
end
end
+31
View File
@@ -0,0 +1,31 @@
# frozen_string_literal: true
module Fission
HEADER_PREFIX = 'HTTP_'
PARAM_HEADER_PREFIX = 'HTTP_X_FISSION_PARAMS_'
PARAMETERS_KEY = 'fission.request.parameters'
class Request < Rack::Request
def headers
Hash[
*env.select { |k,v| k.start_with?(HEADER_PREFIX) }
.map { |k,v| [k.sub(/\A#{HEADER_PREFIX}/, '').split('_').map(&:capitalize).join('-'), v] }
.sort
.flatten
]
end
def params
env[PARAMETERS_KEY] ||= super.merge(path_parameters)
end
def path_parameters
Hash[
*env.select { |k,v| k.start_with?(PARAM_HEADER_PREFIX) }
.map { |k,v| [k.sub(/\A#{PARAM_HEADER_PREFIX}/, '').downcase, v] }
.sort
.flatten
]
end
end
end
+22
View File
@@ -0,0 +1,22 @@
# frozen_string_literal: true
require 'benchmark'
module Fission
CODE_PATH = '/userfunc/user'
module Specializer
def self.call(env)
request = Request.new(env)
request.logger.info("Codepath defaulting to #{CODE_PATH}")
time = Benchmark.measure { load CODE_PATH }
request.logger.info("User code loaded in #{(time.real * 1000).round(3)}ms")
Rack::Response.new([], 201).finish
rescue => e
request.logger.error(%(Specialization failed - #{e}\n#{e.backtrace.join("\n")}))
Rack::Response.new(['500 Internal Server Error'], 500, {}).finish
end
end
end
+5 -50
View File
@@ -1,58 +1,13 @@
# frozen_string_literal: true
require 'rack'
require 'logger'
CODEPATH = '/userfunc/user'
module Fission
class Request < Rack::Request
def headers
Hash[
*env.select { |k,v| k.start_with?('HTTP_') }
.map { |k,v| [k.sub(/\AHTTP_/, '').split('_').map(&:capitalize).join('-'), v] }
.sort
.flatten
]
end
end
class Context
attr_reader :env
def initialize(env)
@env = env
end
def request
@request ||= Request.new(env)
end
end
module Specializer
def self.call(env)
load CODEPATH
Rack::Response.new([], 201).finish
rescue
Rack::Response.new(['500 Internal Server Error'], 500, {}).finish
end
end
module Handler
def self.call(env)
response = if method(:handler).arity > 0
handler(Context.new(env))
else
handler
end
response.is_a?(Array) ? response : Rack::Response.new([response]).finish
rescue
Rack::Response.new(['500 Internal Server Error'], 500, {}).finish
end
end
end
require_relative 'fission/specializer'
require_relative 'fission/handler'
app = Rack::Builder.new do
use Rack::CommonLogger, $stderr
use Rack::Logger, Logger::DEBUG
map "/specialize" do
run Fission::Specializer
+80
View File
@@ -0,0 +1,80 @@
# Binary Environment Examples
The `binary` runtime is a go server that uses a subprocess to invoke executables or execute shell scripts.
For more info read the [environment README](../../environments/binary/README.md).
## Requirements
First, set up your fission deployment with the binary environment.
```bash
fission env create --name binary-env --image fission/binary-env
```
## Example Usage
### hello.sh
`hello.sh` is an very basic shell script that returns `"Hello, World!"`.
```bash
# Upload the function to fission
fission function create --name hello --env binary-env --code hello.sh
# Map /hello to the hello function
fission route create --method GET --url /hello --function hello
# Run the function
curl http://$FISSION_ROUTER/hello
```
This should return a HTTP response with the body `Hello World!`
### echo.sh
`echo.sh` shows the the use of STDIN to read the request body, echoing the input back in the response.
```bash
# Upload the function to fission
fission function create --name echo --env binary-env --code echo.sh
# Map /hello to the hello function
fission route create --method POST --url /echo --function echo
# Run the function
curl -XPOST -d 'Echoooooo!' http://$FISSION_ROUTER/echo
```
This should return a HTTP response with the body `... Echoooooo!`.
### headers.sh
`headers.sh` shows the access to the environment variables that hold the HTTP headers, returning the set HTTP headers.
```bash
# Upload the function to fission
fission function create --name headers --env binary-env --code headers.sh
# Map /hello to the hello function
fission route create --url /headers --function headers
# Run the function
curl -H 'X-FOO: BAR' http://$FISSION_ROUTER/headers
```
This should return a HTTP response with the body `... Echoooooo!`.
### hello..go
This example shows the differences between using shell scripts and binaries. `hello.go` returns `Hello World!` + the
environment variables it received from the server.
```bash
# Build the function targeted at the right architecture
GOOS=linux GOARCH=386 go build -o hello-go-func hello.go
# Upload the function to fission
fission function create --name hello-go --env binary-env --code hello-go-func
# Map /hello to the hello function
fission route create --url /hello-go --function hello-go
# Run the function
curl -H 'X-GO: AWESOME!' http://$FISSION_ROUTER/hello-go
```
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
printf "... "
cat -
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
env | grep "^HTTP_"
+12
View File
@@ -0,0 +1,12 @@
package main
import (
"fmt"
"os"
)
// See README.md in the examples/binary directory for instructions
func main() {
fmt.Println("Hello World!")
fmt.Printf("Environment: %v", os.Environ())
}
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
echo "Hello World!"
+1 -1
View File
@@ -29,7 +29,7 @@ $ chmod +x go-function-build
$ go-function-build hello.go
# Upload the function to fission
$ fission function create --name hello --env go-runtime --package function.so
$ fission function create --name hello --env go-env --package function.so
# Map /hello to the hello function
$ fission route create --method GET --url /hello --function hello
+107
View File
@@ -0,0 +1,107 @@
# Ruby examples
This directory contains several examples to get you started using Ruby
with Fission.
Before running any of these functions, make sure you have created a
`ruby` Fission environment:
```
$ fission env create --name ruby --image USER/ruby-env
```
## Method signature
A standard Ruby function has the basic form:
```ruby
def handler(context)
return [200, {}, []]
end
```
If the fission context is not required, the function can be simplified:
```ruby
def handler
[200, {}, ["Hello, world!\n"]]
end
```
If a simple text response is to be returned, with a status of 200, this
can be further simplified.
```ruby
def handler
"Hello, world!\n"
end
```
## Hello example (`hello.rb`)
This example is the simplest possible Ruby function, as described above.
To run the example:
```
$ fission function create --name hello --env ruby --code examples/ruby/hello.rb
$ fission route create --method GET --url /hello --function hello
$ curl http://$FISSION_ROUTER/hello
Hello, world!
```
## Request data example (`request_data.rb`)
This example shows basic use of the `Fission::Context` and
`Fission::Request` objects.
To run the example:
```
$ fission function create --name request --env ruby --code examples/ruby/request_data.rb
$ fission route create --method GET --url /request/{id} --function request
$ curl http://$FISSION_ROUTER/request/123?key=abc
---ENV---
GATEWAY_INTERFACE=CGI/1.1
PATH_INFO=/
QUERY_STRING=key=abc
REMOTE_ADDR=172.17.0.8
REMOTE_HOST=172.17.0.8
REQUEST_METHOD=GET
REQUEST_URI=http://192.168.64.200:31314/?key=abc
SCRIPT_NAME=
SERVER_NAME=192.168.64.200
SERVER_PORT=31314
SERVER_PROTOCOL=HTTP/1.1
SERVER_SOFTWARE=WEBrick/1.3.1 (Ruby/2.4.1/2017-03-22)
HTTP_HOST=192.168.64.200:31314
HTTP_USER_AGENT=curl/7.52.1
HTTP_ACCEPT=*/*
HTTP_X_FISSION_PARAMS_ID=123
HTTP_X_FORWARDED_FOR=172.17.0.1
HTTP_ACCEPT_ENCODING=gzip
rack.version=1=3
...
HTTP_VERSION=HTTP/1.1
REQUEST_PATH=/
---HEADERS---
Accept: */*
Accept-Encoding: gzip
Host: 192.168.64.200:31314
User-Agent: curl/7.52.1
Version: HTTP/1.1
X-Fission-Params-Id: 123
X-Forwarded-For: 172.17.0.1
---PARAMS---
key=abc
id=123
--BODY--
```

Some files were not shown because too many files have changed in this diff Show More