Compare commits

...
247 Commits
Author SHA1 Message Date
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
Soam Vasani 8f1b27bd72 Remove image tags from yamls 2017-06-21 00:09:31 -07:00
yang qfandSoam Vasani ce4d3ac42d Fix creation of redundant pods on heavy load cold start (#232)
Modifies function specialization so that concurrent requests for the same function cause only one pod to be created.
2017-06-19 18:56:38 -07:00
Soam Vasani 3328014ee6 Fix image version in fission-openshift.yaml 2017-06-17 21:14:27 -07:00
yang qfandSoam Vasani b1e66663bd Retrieve URL params in functions (#226)
Addresses #158. Adds URL params into HTTP headers. A URL param named `KEY` with the value `VALUE` in the actual URL will appear as the header "X-Fission-Params-KEY: VALUE" in the fission function.
2017-06-17 21:12:12 -07:00
yang qfandSoam Vasani c1dd8a9f35 Aggregate tap service request in interval (#229)
The poolmgr client now aggregates successive tapService() calls and sends them to the poolmgr service every 5 sec, instead of sending them immediately.
2017-06-17 20:58:10 -07:00
Ta-Ching ChenandSoam Vasani d9b98830cf Fix http response body not closed correctly, return immediately on error (#210)
* Fix controller timer handler not return when error occurred

* Fix controller not close http response correctly

This PR aims to fix controller not close response correctly. Also, I’ve check all over the project there is no more resource leak issues exist after this patch.

* Fix controller not return immediately when error occurred

* Sort import
2017-06-17 13:10:54 -07:00
S. Brent FaulknerandSoam Vasani a8e7ec534b Add Ruby environment (#223)
Adds a Ruby environment and examples.
2017-06-17 10:48:05 -07:00
Soam VasaniandGitHub 0207d13b23 Specify full golang version in Dockerfiles and build helper script (#227)
Addresses #222.

Plugins and the binaries loading them need to be built with the exact
same go version.

Also update README instructions.
2017-06-09 15:36:09 -07:00
Niko KurttiandSoam Vasani 93ef6fac53 Fix s/Sirupsen/sirupsen/ for logrus (#224)
Fix s/Sirupsen/sirupsen/ for logrus
2017-06-08 13:44:07 -07:00
Ta-Ching ChenandSoam Vasani c43cde8891 Fix pool contains wrong environment metadata (#221)
The generic pool manager creates a generic pool with the reference of environment metadata. Since the reference is address of loop variable, so the content of metadata will be replaced with the last element of envs. So pool manager sets up logging with wrong env.
2017-06-08 11:11:58 -07:00
Soam VasaniandGitHub 62aa9d6d73 Update golang instructions 2017-06-07 10:52:11 -07:00
Javier CastellanosandSoam Vasani cde2f26a9d Added support for pods and replication controllers to watchers (#216) 2017-06-01 10:49:57 -07:00
Mark PeekandSoam Vasani 75d65adcc0 Fix two links in Roadmap doc (#213) 2017-05-31 11:32:32 -07:00
Ta-Ching ChenandSoam Vasani 2f6cd6d659 Print log when timetrigger is removed (#209) 2017-05-30 14:21:41 -07:00
Ta-Ching ChenandSoam Vasani 990d943f21 Retrieve function logs from controller (#207)
Establish a proxy server from the controller to the log database.

Redirect query commands send from client to database then proxy back the db response.

Use parameter binding instead of fmt.Sprintf to prevent SQL injection.
2017-05-30 14:19:22 -07:00
Soam Vasani 531f69a037 Roadmap doc links 2017-05-18 15:21:36 -07:00
Soam Vasani 3ea9ad2449 Roadmap doc formatting 2017-05-18 15:04:01 -07:00
Soam Vasani bc61396071 Roadmap.md for an overview of roadmap and areas 2017-05-18 12:17:33 -07:00
Quinn MurphyandSoam Vasani 6185e842b5 Adding fission-rbac.yml for (#183)
Set up RBAC role bindings to allow fission to create pods in the fission-function namespace.
2017-05-17 16:27:10 -07:00
yang qfandSoam Vasani a8c6347ed0 Add Time Trigger API and client (#153) (#161)
This change adds a timer trigger API, client, and implementation.  Users can trigger a function with a cron-compatible string. 

This change also moves the publisher interface and webhook-based publisher implementation out of kubewatcher and into a separate `publisher` package.
2017-05-17 15:13:56 -07:00
gonrialandSoam Vasani e5f5050ea6 Modify the stock example to show how to change the Content-Type (#124) 2017-05-12 15:36:12 -07:00
yang qfandSoam Vasani 03e3a63581 Move fission-ui intro to Install.md (#159) 2017-05-09 11:36:07 -07:00
mgandSoam Vasani 747a3d2197 Update INSTALL.md with install instructions for openshift (#179) 2017-04-27 14:20:16 -07:00
Fernando AlvarezandSoam Vasani 9bd21d2380 Add OpenShift support (#107) (#168)
Adds the minimal OpenShift components needed to deploy Fission with the
same functionality as with Kubernetes, using only a deployment template
file (fission-openshift.yaml). No need of objects creation via CLI.

Update deployment instructions in README.md
2017-04-27 14:19:32 -07:00
Ashley PenneyandSoam Vasani a15c867812 Make the chart work with helm 2.2 (#170)
Primarily this involved removing the release namespace, which is
controlled by helm.  If you want resources in a namespace you set
--namespace= with helm install, anything else leaves helm unable
to properly manage the resources.

I think the functions namespace won't work either on a delete.
2017-04-27 14:17:30 -07:00
Soam Vasani e1067ab4e5 update guestbook example css 2017-04-25 12:09:19 -07:00
Soam Vasani d2b7dc36ab Update fission.yaml to 3/28 image 2017-04-24 08:25:38 -07:00
syassamiandSoam Vasani 1abe961869 Lighten up the python3 base image (alpine) (#171)
Saves 241 MB from the base example image, bringing things down to 242MB in total.
2017-04-21 11:15:52 -07:00
Soam Vasani 01b399187e Work around 'too old resource version' 2017-03-28 12:42:59 +02:00
Soam Vasani b4dd7a83df Better error checking in watch restarts 2017-03-28 12:06:07 +02:00
Soam Vasani 137d8f7285 Track resourceVersion from event objects 2017-03-28 10:01:11 +02:00
Soam Vasani c9f63e8173 Pass last resource version to restart watch 2017-03-28 10:01:11 +02:00
Soam Vasani a71756762b Restart watches when they stop unexpectedly 2017-03-28 10:01:11 +02:00
Soam VasaniandGitHub a6eb4c6d31 Go build helper script (#163)
Create a docker container and a script to call it, to help people build go plugins for use with fission.
2017-03-27 23:26:59 -07:00
Ta-Ching ChenandSoam Vasani a13015e75a Add function logs support (#53) (#131)
Add function log aggregation and persistence using Fluentd and InfluxDB.

Fluentd and a helper sidecar run as a daemonset.  The poolmgr sets up logging for each function pod, using the helper sidecar. Fluentd forwards logs to InfluxDB, which is run as a deployment and service. The client CLI directly queries InfluxDB for logs.

Fluentd supports many outputs besides InfluxDB, so we aren't very tied to InfluxDB.

The setup is somewhat manual, which we should be able to improve by integrating this into the helm chart.

Diagram of component interactions: https://cloud.githubusercontent.com/assets/202578/23100399/b0e3ea00-f6ba-11e6-8f2f-6588cfef2e84.png
2017-03-22 15:59:42 -07:00
Valentin TjonckeandSoam Vasani 1665235c14 Golang runtime (#125)
Minimal golang runtime.  

Functions are built as Go plugins, and the plugin is uploaded to fission.

See `environments/go/README.md` for instructions and `examples/go` for a usage example.

For now, we're re-using the 'code' field of the function to store the binary plugin. With v2 environments, this will be stored separately as a binary package.
2017-03-21 17:13:29 -07:00
Lingxian KongandSoam Vasani a3f8016442 Set correct Content-Type in the http response (#147)
When the request is handled successfully, json data is returned, this
patch changes the response Content-Type header from text/plain to
application/json

Fixes: #144
2017-03-21 13:51:54 -07:00
Soam Vasani a390c1baa8 Bugfixes in nodejs environment server and examples 2017-03-19 19:36:00 -07:00
J. Gavin RayandGitHub b5b10a4a27 Merge pull request #157 from fission/travis-go-18
Drop Go 1.7, use Go 1.8
2017-03-16 09:25:19 -07:00
Soam Vasani aa77d8686f Drop Go 1.7, use Go 1.8 2017-03-16 09:18:49 -07:00
Robert HerholdandSoam Vasani 68c006cd33 Add README for Node.js examples (#155) 2017-03-14 17:32:13 -07:00
maxwellandSoam Vasani b561f7d948 use fmt.Errorf instead of error.New() (#149) 2017-03-14 16:23:32 -07:00
Robert HerholdandSoam Vasani 9bdaf3c74e Upgrade node environment to Node.js 7.6.0+ (#151)
Upgrades the node environment to NodeJS 7.6.0.  Functions can now use async/await and promises; they can return a promise instead of using a callback.  The change preserves compatibility with the callback style.  

* Upgrade node environment to Node.js 7.6.0+

* Bump package version

* Preserve compatibility for callbacks

* Cleanup

* Re-add hello callback example

* Make sure not returning won't blow everything up

* Update stock example with request promise
2017-03-14 16:00:26 -07:00
Robert HerholdandSoam Vasani e1cb5f6e09 Return HTTP status 201 for created resources (#148)
Have the controller return status 201 for created resources, and have the client check for 201 response on created resources.

#143
2017-03-12 20:05:19 -07:00
yang qfandSoam Vasani a6869ab746 Fix function delete with uid (#142)
If no uid is provided, all versions of function files and function metadata will be deleted.  If a uid is provided, only that specific version is deleted, and the "latest" version of the function moves back to the previous one.

Fixes #152
2017-03-12 13:04:56 -07:00
Robert HerholdandSoam Vasani 365eabb027 Make it more clear where to clone this repo (#145)
Make it clear that the repo should be cloned into the $GOPATH.
2017-03-08 15:30:41 -08:00
Ta-Ching ChenandSoam Vasani ad63ba63c8 Fixed pod has no ip (#139) (#141)
Fixes #139.  The code before this change was saving up pointers to the loop variable in a slice. So loop was useless and we always chose the last pod in the list, whether it was ready or not.  With this fix, choosePod should always return a ready pod.

Also adds a check for the pod phase, before looping over the containers in the pod, and discard pods that aren't running yet.
2017-03-05 11:01:47 -08:00
Simon DittlmannandSoam Vasani 6af605bfe8 fix kubeEventsSlack example: typo and wrong variable name (#140)
- change comma to semicolon
- change variable name `path` to `slackWebhookPath`
2017-03-04 14:08:23 -08:00
Robert HerholdandSoam Vasani 4d16a4be08 Fix Markdown table (#136) 2017-03-02 11:35:59 -08:00
Robert HerholdandSoam Vasani 6e5b1cf8db Ignore the vendor folder (#137) 2017-03-02 11:33:11 -08:00
Soam VasaniandGitHub ca3bf8e83e Symlink user function's node_modules to server's node_modules (#133)
Fixes #132
2017-02-23 10:19:42 -08:00
Soam Vasani 92cd5334ec Add a default handler for the / route
Before this change, if the user doesn't define a route for /, the
router happily 404s at that path.  This breaks GKE Ingress, which uses
that request as a health check.  So this change adds a handler for
"GET /", unless the user already has one.

Also, log the delay for slow requests.
2017-02-20 23:57:40 -08:00
Sanket SudakeandSoam Vasani f4b6558e79 Remove redundant hello.js from charts directory (#130) 2017-02-20 15:14:56 -08:00
Soam Vasani 3b75a86773 Instructions in the README were leading to a malformed file
Switch to using a curl command to download sample code, instead of
dealing with nested quotes and stuff.

Addresses #128.
2017-02-17 15:34:02 -08:00
Soam VasaniandGitHub 8cf382c2b3 Handle errors in filestore init (#127)
Fixes issue #108
2017-02-17 15:21:36 -08:00
Toby CrawleyandSoam Vasani ab6e2e8021 Improve command-line client error output (#122)
Motivation: The output when an error occurs shows an integer error code,
which isn't very helpful. And in the case of an error when creating a
resource, you also get a redundant log message from the controller
client.

Modifications:
* the controller client no longer logs errors
* the generated String() allows for the enum name to be displayed in the
  error output
* each errorCode enum member has a hand-generated description
* the format of the error message from the CLI client was modified to
  display the error description with the error message

Related to #112
2017-02-16 13:58:59 -08:00
Soam Vasani eae5150bff Initial test script
Test script that sets up fission on a cluster, runs tests and
uninstalls it.

Yet to come:

1. The actual tests
2. Builds (for fission-bundle, client, and envs)
3. Automation with github pull requests
2017-02-16 12:55:38 -08:00
Soam Vasani d4dc166039 Use Helm's built-in support for namespaces
Remove our non-standard namespace variable and use .Release.Namespace
to put fission deployments and services into.

(We still need the functionNamespace parameter, to customize what
namespace fission puts functions into.)
2017-02-16 12:01:26 -08:00
Soam Vasani 41baa7b6bf Add fission and fission-function namespaces to template variables
Also, update readme.
2017-02-16 11:08:47 -08:00
Soam Vasani 6e4115cb5d Update use cases, talk a bit about implementation 2017-02-15 11:21:51 -08:00
Kaustubh PhatakandSoam Vasani 9e5db82cc9 Check for unique HTTP route & method (#102) (#111)
Check for uniqueness in HTTP routes, returning error 409 when a route + method combination is duplicated.
2017-02-13 15:58:19 -08:00
Matt RickardandSoam Vasani 6be861afc8 Upgrade minikube version in docs to v0.16.0 (#120) 2017-02-13 14:17:33 -08:00
Toby CrawleyandSoam Vasani 430639a1cf Report KeyNotFound from etcd as a 404 (#121)
This allows for proper classification of the error on the client side.
2017-02-13 13:16:12 -08:00
Toby CrawleyandSoam Vasani c8b5bb2972 Better convey duplicate name errors to client (#116)
Motivation: Creating duplicate resources currently results in a 500
error being displayed by the client, with no information about the true
nature of the failure.

Modifications:
* ResourceStore now converts any errors from the etcd client into
  fission errors, capturing the reason for the error in the case of a
  duplicate key (as ErrorNameExists)
* ErrorNameExists errors are signaled to the client with a
  409 (Conflict) HTTP status
* MakeErrorFromHTTP() now reads the body of the error response to
  retrieve the actual error message instead of using the HTTP status
  message
* the controller client now uses MakeErrorFromHTTP() to centralize
  status code -> error code mapping
* tests for all of the resources now check that duplicate resources are
  reported properly

Result: The client can now provide more context when a duplicate name is
given

related to #112
2017-02-13 08:58:52 -08:00
Sanket SudakeandSoam Vasani 99d6a95bc1 Helm chart for fission (#90)
Helm Chart for Fission.  See NOTES.txt for usage.
2017-02-12 00:13:24 -08:00
Ta-Ching ChenandSoam Vasani 14c55912f5 Fix failure to delete function when function's file doesn't exist (#118) 2017-02-10 23:38:13 -08:00
Soam VasaniandGitHub 858fe9f62b Merge pull request #117 from tobias/gitignore-updates
Update gitignore to include dev artifacts
2017-02-10 14:10:50 -08:00
Toby Crawley 25afe7bd78 Update gitignore to include dev artifacts 2017-02-10 13:53:46 -05:00
Soam VasaniandGitHub 3420a614bb Merge pull request #114 from fission/issue-113
Don't wait for ready pod in MakeGenericPool
2017-02-09 16:27:17 -08:00
Soam Vasani d4d917ad04 Don't wait for ready pod in MakeGenericPool
Addresses issue #113.  poolmgr.MakeGenericPool does not need to wait
for a ready pod.  Kubernetes already retries image fetching, so
there's no need to repeat the task in fission.  And if it's a
non-retriable error (like the image URL being wrong), then the
deployment will stay broken, and the user can fix the environment with
'fission env update' (or 'fission env delete' followed by 'fission env
create').
2017-02-09 15:27:57 -08:00
Soam Vasani d0ad377d72 Add README to NodeJS env 2017-02-08 16:43:19 -08:00
Soam VasaniandGitHub 4046c811b1 Merge pull request #109 from tobias/build-sh-as-sh
Make build an actual sh script
2017-02-06 18:02:25 -08:00
Soam VasaniandGitHub 8127a1f57f Merge pull request #110 from tobias/readme-updates
Minor improvements to build instructions in README
2017-02-06 18:01:23 -08:00
Toby Crawley 20cac31635 Minor improvements to build instructions in README 2017-02-06 15:33:46 -05:00
Toby Crawley fd07a8a75e Make build an actual sh script
This allows it to be executed from alternative shells (fish, for
example).
2017-02-06 15:28:46 -05:00
Soam VasaniandGitHub 0d4d2e32a8 Merge pull request #106 from kaustubhvp/fix-func-validations
Fixing validations of fn actions
2017-02-06 09:12:43 -08:00
Soam VasaniandGitHub 436057e15b Merge pull request #105 from ktrance/master
Http request support
2017-02-06 09:12:00 -08:00
Kaustubh Phatak 33f7647ea7 Fixing validations of fn actions 2017-02-05 12:51:52 -08:00
Klavs Madsen a1d255f038 Added usage examples
* Cleaned up project.json

* Updated README with example on how to deserialize json request body

* Added reference to System.Runtime.Serialization.Json as this would seems a common usage

* Added C# example files to the /Examples directory
2017-02-05 12:57:49 +01:00
Klavs Madsen c9ea73786b Http request support
Added support for accessing basic http request information
2017-02-04 17:58:03 +01:00
Kaustubh PhatakandSoam Vasani 76d4ea7b04 function code download using HTTP URL (#100)
* function code download using HTTP URL

* Addressing review comments

* Updating help-text for function
2017-02-02 15:14:00 -08:00
Damien GoujardandSoam Vasani 2725da6b1b Add initial support for PHP7 (#97)
* Add initial support for PHP7

* Update PHP env with now an PSR3 logger (Monolog). Now you can also create a handler function with PSR7 Http message and PSR3 logger in parameter.

* Add examples for PHP7 environement

* For development you can create a function in this path /app/userfuncdev.php it will be loaded

* Switch to alpine base image

* handler now receive an array with request, response and logger object
2017-02-02 11:28:28 -08:00
Laurent CrisciandSoam Vasani c353e7f230 Error when env name/image not provided (#98)
I also renamed image to envImg for consistency
2017-02-02 10:23:16 -08:00
Soam VasaniandGitHub 7b58ef389d kubewatcher example: send watch updates to slack (#96)
This example sends change events from a watch to a Slack webhook.
2017-01-31 12:23:45 -08:00
Soam VasaniandGitHub 9db35b6ff2 Merge pull request #94 from andrewstuart/bugfix/https
bugfix (cli) Update the URL check to work with https
2017-01-31 11:37:08 -08:00
Andrew Stuart 1cc1fac151 Satisfy golint, clarify logic 2017-01-31 08:25:57 -07:00
Andrew Stuart 39fb45d8da bugfix (cli) Update the URL check to work with https 2017-01-31 08:11:54 -07:00
Soam Vasani 1b04f042cd Note limitations of dynamic loading in pods 2017-01-26 22:18:38 -08:00
Soam VasaniandGitHub 7485dca7d8 Merge pull request #84 from ktrance/master
Added support for running C# code in a dotnet core environment
2017-01-26 11:39:21 -08:00
Soam VasaniandGitHub 20baa2ee9d Wait for Pod IP while waiting for pod ready (#89)
Addresses issue #88.
2017-01-25 15:32:34 -08:00
Soam Vasani 2fc9fd9603 Update YAML to point at alpha20170124 fission-bundle image 2017-01-24 11:27:01 -08:00
Soam VasaniandGitHub 0465ccd352 Merge pull request #87 from AlekSi/patch-1
Make go vet happy
2017-01-24 08:40:55 -08:00
Alexey PalazhchenkoandGitHub 9e01b2f1a7 Make go vet happy 2017-01-24 17:11:26 +03:00
Soam Vasani cb08118164 Update README, create separate install instructions 2017-01-23 23:55:23 -08:00
Soam Vasani 043ae0c2b1 Add a contributing.md 2017-01-23 22:36:15 -08:00
Soam Vasani bba71d65a2 Fix incorrect log printf 2017-01-23 19:49:13 -08:00
Soam VasaniandGitHub f3e1f8b88c Merge pull request #86 from fission/travis-failure
Ignore glide cache in gofmt check
2017-01-23 19:43:47 -08:00
Soam Vasani da15e5cdb7 Ignore glide cache in gofmt check 2017-01-23 19:29:45 -08:00
Soam Vasani 90e7f0212a Programming model doc update 2017-01-23 19:13:02 -08:00
Soam Vasani 000b74d4ad Doc updates 2017-01-23 19:11:37 -08:00
Klavs Madsen 760e021141 Added basic logging, just a facade for now
Added FissionContext class
Changed name on both class and function for input code to avoid namespace clashes when adding FissionContext
Updated README
Added section on how to develop/debug the code to README, might be obvious for some but not all
Changed code input from static function to method
2017-01-22 15:45:08 +01:00
Klavs Madsen f5270d97b1 Added support for running C# code in a dotnet core environment 2017-01-21 13:57:45 +01:00
Soam VasaniandGitHub 8339e120ce Merge pull request #81 from fission/bugfix-internal-routes
Bugfix for internal routes
2017-01-19 09:54:00 -08:00
Soam Vasani 1d4875ff74 Bugfix for internal routes
Poolmgr removed the default-latest-version semantics to simplify
caching.  But this broke the router's internal function routes, which
are used by kubeWatcher.  The router now exposes a versionless url
and regularly updates that to point to the latest version.
2017-01-19 09:27:05 -08:00
Soam Vasani e0c7682996 Improve environment doc 2017-01-18 16:59:39 -08:00
Soam Vasani 4f9b36b042 Add README to python env 2017-01-18 16:53:03 -08:00
Soam Vasani e268ae68e7 Create python env conditionally 2017-01-18 15:43:29 -08:00
Soam Vasani 928d96c080 A trivial guestbook app
Functions for GET and POST endpoints. A redis.yaml to deploy redis on
kubernetes.  And a deploy.sh to deploy the fission functions with
routes.
2017-01-18 15:30:51 -08:00
Soam Vasani 9ffba72c11 Add redis client to python env 2017-01-17 23:42:05 -08:00
Soam VasaniandGitHub b1cf2a0182 Merge pull request #79 from fission/router-handle-method-bug
Bug fix for handling a route's HTTP method in router
2017-01-17 22:27:44 -08:00
Soam Vasani 3ed5e772cf Update test to include method 2017-01-17 22:06:02 -08:00
Soam Vasani 3f02ccaf34 Bug fix for handling a route's HTTP method in router 2017-01-17 18:42:54 -08:00
Soam VasaniandGitHub 324af61192 Merge pull request #77 from frodenas/namespace-flag
fission-bundle: allow setting the namespace
2017-01-13 11:22:37 -08:00
Ferran Rodenas 86a8430c52 fission-bundle: allow setting the namespace 2017-01-13 13:10:13 +01:00
Soam Vasani 93ca9cbf3e Add slack badge 2017-01-12 11:54:46 -08:00
Soam VasaniandGitHub 9ea7c80977 Merge pull request #75 from fission/cleanup-deleted-pools
Delete generic pools when environments are deleted
2017-01-11 13:42:44 -08:00
Soam Vasani 5460f8ac05 Delete generic pools when their environment is deleted 2017-01-11 13:35:28 -08:00
Soam Vasani 6af56753ea Cleanup deleted pools, partly implemented 2017-01-09 16:17:30 -08:00
Soam VasaniandGitHub ac4c11bfb4 Merge pull request #70 from fission/poolmgr-delete-pods-on-failure
Poolmgr: fix pod leak bugs on specializePod failure
2017-01-06 23:31:45 -08:00
Soam Vasani 0fb12af4e5 add log on specializePod error/clean up 2017-01-06 22:16:49 -08:00
Soam Vasani 3e386fb41d Handle pod cleanup on svc creation error 2017-01-06 22:09:17 -08:00
Soam Vasani e4f7027a69 Reorganize a bit to avoid duplicated error handling 2017-01-06 21:54:15 -08:00
Soam Vasani ebb486d121 Delete pods that failed to load user function 2017-01-06 17:43:03 -08:00
Soam VasaniandGitHub d5516686c8 Merge pull request #69 from fission/poolmgr-gc
Poolmgr: ensure orphaned resources are cleaned up
2017-01-06 17:25:06 -08:00
Soam Vasani 37a00b6116 Add cleanup.go 2017-01-06 17:14:54 -08:00
Soam Vasani eccde4fe74 Poolmgr: clean up orphaned resources on restart
Label all poolmgr-created resources with an id that's unique to a
running poolmgr instance.  On poolmgr start up, clean up resources
created by old instances.  Resources that are idle are killed
immediately; resources that could be running a user function are
killed after the maximum function timeout.
2017-01-06 17:01:21 -08:00
Soam VasaniandGitHub 595e634617 Merge pull request #68 from fission/cli-route-update
Implement 'fission route update'
2017-01-05 16:15:19 -08:00
Soam Vasani e73242f85c Implement 'fission route update'
"fission route update" can now be used to update a route to point to a
different function.
2017-01-05 15:59:39 -08:00
Soam VasaniandGitHub 5829552c3b Merge pull request #67 from fission/function-versioning-cache-bug
Update router cache on new function version
2017-01-05 15:27:29 -08:00
Soam Vasani fa272f20a0 Update router cache on new function version
Addresses issue #51. Makes versioning in poolmgr explicit, so its
cache needs no invalidation on version update.  Functions in poolmgr
are always cached by name and UID.

Router now updates implictly versioned routes with the latest version
of a function.

This means that users will see requests to implicitly-versioned routes
go to the latest version of a function within 3 seconds.  (Those 3 sec
will go away when we use a real watch instead of polling the
controller.)

There's no change in behaviour for routes that explictly specify a
function UID.
2017-01-05 15:02:16 -08:00
Soam VasaniandGitHub cb5cae6501 Merge pull request #66 from fission/changeOrg
Changed Package Names to Match new Github Organization
2016-12-24 10:50:41 -08:00
J. Gavin Ray ea2bb9952c Changed package names and documentation to match the new git organization 2016-12-24 08:12:15 -08:00
J. Gavin Ray 943d430870 # This is a combination of 2 commits.
# This is the 1st commit message:

Changed package names to match the new organization

# This is the commit message #2:

Forgot Cache
2016-12-24 08:11:35 -08:00
J. Gavin RayandGitHub c7d2216400 Updated glide.yaml
Changed package to reflect the new organization.
2016-12-24 07:38:33 -08:00
Soam Vasani 63d7c666e0 Merge branch 'code-of-conduct' 2016-12-21 12:56:01 -08:00
Soam Vasani 3e24cbcca2 Add code of conduct (contributor covenant, v1.4)
We still need to write a nice contributor guide; for now let's just
add a code of conduct.
2016-12-21 12:53:42 -08:00
Soam VasaniandGitHub 08d1e55251 Merge pull request #65 from platform9/route-create-with-fn-create
Add HTTP route create params to function create command
2016-12-20 19:36:48 -08:00
Soam Vasani a5d96b928c Add HTTP route create params to function create command
This allows users to add --method and --url params to their "fission
fn create" command line.  It sets up the route to call the latest
version of the function; user can edit the route with the "fission
route update" command, if they want to.
2016-12-20 17:42:23 -08:00
Soam VasaniandGitHub f0b31564f0 Merge pull request #56 from platform9/kubewatcher
Kubewatcher: trigger functions from Kubernetes Watch callbacks
2016-12-20 14:30:32 -08:00
Soam Vasani d721df998d Fix object-type header passed to event handlers 2016-12-20 14:16:33 -08:00
Soam Vasani c5425180e8 Make webhook publisher use relative urls 2016-12-20 12:19:51 -08:00
Soam Vasani 8704761fd0 watch.Url renamed to watch.Target 2016-12-20 12:11:54 -08:00
Soam Vasani 4d75c3fa4e Make internal urls relative (as before) 2016-12-20 12:11:22 -08:00
Soam Vasani 0456df0d63 Refactor kubewatcher/webhook publisher
Create a Publisher interface. Refactor webhook publisher to satisfy
that interface.

Add some limited retries to webhook publisher.
2016-12-19 14:38:44 -08:00
Soam VasaniandGitHub 7493759134 Merge pull request #61 from platform9/add-kubectl-instructions
Add kubectl download to install instructions
2016-12-16 17:47:16 -08:00
Soam Vasani 1fe894ab1c Add kubectl download to install instructions 2016-12-16 17:42:35 -08:00
Soam VasaniandGitHub cbc4749779 Merge pull request #60 from platform9/readme-minikube-instructions
Readme minikube instructions
2016-12-16 15:24:06 -08:00
Soam Vasani 1c5954ae61 mac/linux instructions 2016-12-16 14:53:34 -08:00
Soam Vasani d503834901 mac/linux download url for minikube 2016-12-16 14:41:12 -08:00
Soam Vasani dcc17d50b8 Include minikube setup instructions 2016-12-16 14:22:40 -08:00
Soam VasaniandGitHub ea4a9faceb Merge pull request #59 from platform9/fission-cli-bug
Check for name in 'function delete'
2016-12-16 13:50:40 -08:00
Soam Vasani f84827e384 Check for name in 'function delete' 2016-12-16 13:43:00 -08:00
Soam VasaniandGitHub 943b15f5a7 Merge pull request #57 from kaustubhvp/adding-go-report-card
adding go-report card and fixing minor typo in README
2016-12-16 09:35:19 -08:00
Soam Vasani dddb50c00f cli watch impl 2016-12-16 00:54:06 -08:00
Soam Vasani de30886c9e compile error 2016-12-16 00:43:45 -08:00
Kaustubh Phatak 96cc8e402e adding go-report card and fixing minor typo in README 2016-12-15 20:57:08 -08:00
Soam Vasani 215f4fc30f comments 2016-12-15 19:48:45 -08:00
Soam Vasani 53c49d1485 Add an atomic bool to track if a watch is stopped
Saw a watch channel close once without actually calling watch.Stop.
Need to investigate this a bit and handle it if necessary.
2016-12-15 09:34:03 -08:00
Soam Vasani 4bc7480acd Add request logging to router 2016-12-15 09:33:02 -08:00
Soam Vasani 5c6dcca19d Pass obj type to functions 2016-12-12 08:46:19 -08:00
Soam Vasani b7906905c2 Make the internal triggers point to the latest version 2016-12-11 19:59:10 -08:00
Soam Vasani f1e1da4ceb Bugfix in fission-bundle arg parsing 2016-12-11 19:58:26 -08:00
Soam Vasani 5e3adc0b59 Log on function exception 2016-12-11 19:58:04 -08:00
Soam Vasani 0fb4cc1536 Use latest version of function for watches 2016-12-11 00:07:25 -08:00
Soam Vasani 193cf594be Let fission CLI tolerate URL with/without leading http:// 2016-12-10 19:56:45 -08:00
Soam Vasani 7918b89fd2 Bugfix in kubewatcher deployment yaml 2016-12-10 19:56:15 -08:00
Soam Vasani 2379cb6f81 Add watches to fission CLI 2016-12-10 19:55:43 -08:00
Soam Vasani f29e69c581 Fix cleanup on duplicate function specialization 2016-12-10 19:55:20 -08:00
Soam Vasani ddb189bda2 Kube requests: add content-type 2016-12-10 19:54:01 -08:00
Soam Vasani 188280488c Fix bug in populating watch url 2016-12-10 19:53:13 -08:00
Soam Vasani 9dcd2b711f Add kubewatcher deployment to yaml 2016-12-09 23:58:31 -08:00
Soam Vasani 4ae0ab98e8 Add kubewatcher to fission-bundle 2016-12-09 23:43:27 -08:00
Soam Vasani a69834157f Controller crud for Watch type
Controller and client crud, API routes, and a unit test for the Watch
type.
2016-12-09 23:42:03 -08:00
Soam Vasani 3a6d5bb070 Add "internal" routes for all functions
These routes allow us to call any function without explicitly defining
a route for them.  TODO: if we can serve these routes from a separate
instance of router (controlled by a commandline flag or env var), we'd
be able to keep this instance of the router "private" by not assigning
it a loadbalancer/nodeport or other externally-visible service.
2016-12-09 23:34:46 -08:00
Soam Vasani e49af8ca78 Watch type
Contains parameters to start a Kubernetes watch, and a function to
call when the watch triggers.
2016-12-09 23:25:29 -08:00
Soam Vasani fa093829bc Kubernetes events watcher
Kubewatcher watches the Kubernetes API for Watches that have been
configured on the fission controller.  It then posts that event to the
function configured in the watch.
2016-12-09 23:22:18 -08:00
Soam VasaniandGitHub 85b0a239c0 Merge pull request #49 from kaustubhvp/py-common-libs
adding commonly used python libraries
2016-12-08 17:00:21 -08:00
Kaustubh Phatak cfd503af8b removing stevedore and websocker-client libs from python env 2016-12-08 15:42:19 -08:00
Kaustubh Phatak 01249c2d4a adding commonly used python libraries 2016-12-08 15:27:04 -08:00
Soam VasaniandGitHub 88f33b9382 Merge pull request #48 from platform9/python-setup-logger
Setup app.logger for python environment

Fixes #47
2016-12-08 14:22:00 -08:00
Soam Vasani 4a6a088eca Modify example a bit 2016-12-08 14:13:01 -08:00
Soam Vasani 5f02f6c8ca Setup app logger in python environment
The user's functions can now use `current_app.logger`, and use
`kubectl logs` to see the logs that the function outputs.
2016-12-08 14:03:27 -08:00
Soam Vasani ab96ed2917 Add a python example showing how to get at request data 2016-12-08 11:59:59 -08:00
Soam VasaniandGitHub e8a84dcaa3 Merge pull request #46 from platform9/add-build-badge
Add build badge
2016-12-05 09:57:56 -08:00
Soam Vasani e627a55988 move status badge 2016-12-05 09:31:42 -08:00
Soam Vasani ed426db4a4 Add travis build status badge back to README 2016-12-04 20:12:05 -08:00
Soam VasaniandGitHub 5e56d99415 Merge pull request #45 from platform9/run-etcd-in-travis
Install and run etcd on travis
2016-12-04 18:44:48 -08:00
Soam Vasani cd5325090b Background etcd in travis run script 2016-12-04 18:33:50 -08:00
Soam Vasani 4c14f34969 Install and run etcd on travis 2016-12-04 18:19:03 -08:00
Soam VasaniandGitHub f3aeb28add Merge pull request #44 from platform9/poolmgr-test
Bugfix in functionServiceCache test
2016-12-04 15:31:58 -08:00
Soam Vasani 571fa6b0e7 function service cache test bugfix 2016-12-04 15:23:55 -08:00
Soam VasaniandGitHub ea03e0abc2 Merge pull request #42 from platform9/travis-test
#25 Continuous Testing
2016-12-04 14:58:16 -08:00
J. Gavin Ray e92521deab #25 - Addressing suggestions 2016-12-04 14:44:22 -08:00
Soam VasaniandGitHub dd08d07d59 Merge pull request #43 from platform9/fix-cache-test
Fix cache test
2016-12-04 14:15:22 -08:00
Soam Vasani 675cc4f360 Fix cache test
cache.Set() returns existing value; update the test accordingly.
2016-12-04 14:10:02 -08:00
J. Gavin Ray f4be8a73b3 added build status to README 2016-12-04 12:49:31 -08:00
J. Gavin Ray 69cd025a66 added Go versions to test against 2016-12-04 12:04:17 -08:00
Soam VasaniandGitHub c8c0afc68f Merge pull request #41 from carmark/travis
Add travis integration test and fix the tag for client-go in glide.yaml.
2016-12-03 23:10:41 -08:00
Lei Xue e9c29a8ec8 add travis test 2016-12-04 14:28:20 +08:00
Lei Xue 3478d0643b update glide.yaml with k8s client tag v1.5.0 2016-12-04 14:28:09 +08:00
Lei Xue c4b7043e70 format poolmgr/gp.go 2016-12-04 14:27:51 +08:00
Soam VasaniandGitHub 5a007184d4 Merge pull request #39 from pirogoeth/patch-1
Fix `environment` command typo.
2016-11-30 20:01:18 -08:00
Sean JohnsonandGitHub 08da045f43 Fix environment command type. 2016-11-30 18:51:44 -06:00
Soam VasaniandGitHub 3672abbe82 Merge pull request #38 from platform9/edit-readme
Streamline the README's setup instructions a bit.  Add links to Mac & Linux CLI binaries.
2016-11-30 16:12:12 -08:00
Soam Vasani 36e056c1f4 make cli download into 1 copy-pasteable command 2016-11-30 16:00:41 -08:00
Soam Vasani 87e2daf455 minor readme edits 2016-11-30 15:47:17 -08:00
Soam Vasani a37b71e063 link to gke; other readme edits 2016-11-30 15:45:48 -08:00
Soam Vasani 654763cac1 Mac/linux CLI 2016-11-30 14:30:00 -08:00
Soam Vasani 460db87bd7 Merge env var section to setup 2016-11-30 14:13:00 -08:00
Soam Vasani 3aa0c2e803 Streamline setup instructions a bit 2016-11-30 13:48:42 -08:00
Soam VasaniandGitHub 9e452fd325 Merge pull request #36 from efexen/patch-1
Updated README to include protocol scheme for FISSION_URL prefix
2016-11-29 21:00:48 -08:00
Soam VasaniandGitHub e94f0c0d6f Merge pull request #34 from johscheuer/update-readme
Add minikube example in readme; split fission.yaml into core + one for LoadBalancer services and another for NodePort services.
2016-11-29 20:52:53 -08:00
Ville Hellman (FxN)andGitHub cca8c0dad1 Updated README to include protocol scheme for FISSION_URL prefix 2016-11-29 22:09:19 +00:00
Johannes Scheuermann 6593c15e23 Add minikube example in readme 2016-11-29 21:19:45 +01:00
Soam VasaniandGitHub 18dd175c9f Merge pull request #32 from philips/patch-1
README: use kubectl create -f http
2016-11-28 18:50:58 -08:00
Brandon PhilipsandGitHub ab3f45936a README: use kubectl create -f http
Instead of getting curl involved just use kubectl
2016-11-28 17:39:14 -08:00
Soam Vasani 84851922f3 Fixes #31: Explicitly tag fission-bundle image in fission.yaml 2016-11-28 14:52:13 -08:00
Soam VasaniandGitHub 53e9ba6283 Merge pull request #30 from platform9/python-improvements
Python environment improvements
2016-11-19 20:15:40 -08:00
Soam Vasani 640e65f35f Use Flask for Python environment
Flask gives us easy routing support, and simple interface for getting
at request data.
2016-11-19 20:04:46 -08:00
Soam Vasani 8a9559be1f Pass a valid context 2016-11-19 11:01:28 -08:00
Soam VasaniandGitHub 04efe0bad4 Merge pull request #29 from platform9/readme-updates
Readme updates
2016-11-17 16:36:55 -08:00
Soam Vasani a2df49c04f typo 2016-11-17 16:35:56 -08:00
Soam Vasani c213f18bbb k8s benefits 2016-11-17 16:33:48 -08:00
Soam Vasani c3187a2d23 web and twitter links 2016-11-17 16:31:24 -08:00
Soam Vasani 05bc663b3f readme updates 2016-11-17 16:28:41 -08:00
Soam Vasani 3b5139dca3 readme updates 2016-11-17 16:22:10 -08:00
Soam Vasani c733864df5 readme updates 2016-11-17 16:11:56 -08:00
Soam Vasani 6cdae1ae3a Update README with some more project explanation 2016-11-17 16:07:42 -08:00
Soam VasaniandGitHub 54bf4abbcb Merge pull request #28 from platform9/cli-function-edit
Add "fission function edit <function>" command
2016-11-15 16:16:56 -08:00
Soam Vasani 1a19f718d4 Add a "fission function edit" command
The new command invokes editor on a copy of the source code, and
uploads the new source code.  It's an easy way to manually edit
functions from a terminal.
2016-11-15 15:51:47 -08:00
Soam Vasani 2a9f229c17 Fix incorrect JSON in version message 2016-11-15 14:33:31 -08:00
Soam Vasani 2eceb8263d Remove unused imports 2016-11-15 14:21:08 -08:00
Soam Vasani d6efca07a2 Add a tag argument to push.sh 2016-11-15 14:05:05 -08:00
Soam Vasani 96e14180c4 Don't check controller filestore path
It's created on-demand by fileStore.
2016-11-15 14:04:32 -08:00
Soam VasaniandGitHub 679590883d Merge pull request #27 from platform9/move-to-latest-client
Move client-go dependency to 1.5
2016-11-14 16:33:39 -08:00
Soam Vasani 9aab4f0795 Move client-go dependency to 1.5
Client 1.5 is compatible with Kubernetes version 1.3 through 1.5.
2016-11-14 16:25:23 -08:00
Soam Vasani 92846da7c2 README formatting 2016-11-12 10:38:52 -08:00
Soam VasaniandGitHub 26aa0998fd Use http for fission.io link
fission.io is on github pages, which doesn't seem to support SSL on custom domains.  Use http for now.
2016-11-12 08:43:27 -08:00
Soam Vasani 87a7fa6e26 Use glide for dependency mgmt; update README
And compilation instructions to README.
2016-11-11 14:15:59 -08:00
173 changed files with 10632 additions and 643 deletions
+11
View File
@@ -1 +1,12 @@
fission-bundle/fission-bundle
# Pycharm IDE
.idea
environments/php7/vendor/
# Common backup files
*.swp
*.bak
*.tmp
*~
vendor/
+20
View File
@@ -0,0 +1,20 @@
sudo: required
dist: trusty
language: go
go:
- 1.8
- tip
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
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')
+74
View File
@@ -0,0 +1,74 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
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
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.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
+46
View File
@@ -0,0 +1,46 @@
# Contributing to Fission
### Thank you!
Thank you for taking the time and effort to contribute to Fission!
First, please read the [code of conduct](CODE_OF_CONDUCT.md). By
participating, you're expected to uphold this code.
### Choose something to work on
The easiest way to start is to look at existing
[issues](https://github.com/fission/fission/issues) and see if there's
something there that you'd like to work on.
Also, if you're familiar with a language that we don't support today,
adding an environment for a new language is a good task to take on.
Take a look at the Python environment as an example.
If you're interested in working on something that doesn't have an
existing issue, feel free to file a new one.
There's plenty of stuff to do. If you need help choosing something to
work on, we'll be happy to suggest something depending on your
interests.
### Talk to people
At any point, from thinking about contributing to merging your
changes, please feel free to reach out to us! We're more than happy
to talk.
If you choose a big chunk of work that involves design changes to
Fission, please talk to us while planning the change; that way we can
all be on the same page before you invest too much work into it.
* Drop by the [slack channel](http://slack.fission.io)
* Say hi on [twitter](https://twitter.com/fissionio).
* Email soamvasani@platform9.com
### Contribution Howto
* Fork this github repo
* Make a branch in your fork
* Commit your changes to that branch
* Create a pull request
+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
```
+96 -59
View File
@@ -1,107 +1,144 @@
A high level view of the internals of Fission.
How it works
============
Fission is a FaaS -- users create functions (source level), register
them with fission using a CLI, and associate functions with triggers.
Fission wraps those functions into a service, and runs them on
Kubernetes on demand.
Here's an overview of the services that make up fission.
Components
==========
Language-neutral components:
* Controller
* Container Pool Manager
* Container Specializer
* Router
* controller
* poolmgr
* router
* kubewatcher
Language-specific components:
* Language Build Container
* Language Run Container
* Environment container
Controller
----------
Function and Trigger CRUD APIs. APIs to watch for changes are also
included (useful for other components that cache state).
See api/swagger.json for API details.
The controller contains CRUD APIs for functions, http triggers,
environments, Kubernetes event watches. This is the component that
the client talks to.
This is the only stateful component. It needs to be configured with a
URL to an etcd cluster and a path to a persistent volume. The volume
will be used to store the functions' source code.
is used to store the functions' source code. Etcd is used as the DB.
Etcd is used as the DB.
[Work to extend to other storage backends is planned, see issue #83.]
Pool Manager
------------
Container Pool Manager
----------------------
Poolmgr manages pools of generic containers and function containers.
Manage pool of generic containers.
It has a simple API; both these endpoints are called by the router.
Probably use K8s RCs. Can we Use labels to move pods from one rc to
another? What about jobs?
* GetFunctionService takes function metadata and returns the address
of a service.
* TapService lets poolmgr know a service is being used; if it's not
called for a few minutes the pod(s) backing the service are killed.
Container Specializer
---------------------
Poolmgr watches the controller API and eagerly creates generic pools
for environments. It uses Kubernetes deployments to do that. The
environment container runs in a pod with the 'fetcher' container.
Fetcher is a very simple utility that downloads a URL sent to it and
saves it at a configured location.
Inputs: a running generic language run container, a user function,
optionally an http trigger URL.
Calls Language Run Container and sets up Router to point to it.
GetFunctionService "specializes" a pod. The implementation chooses a
pod from the pool, relabels it to "orphan" the pod from the
deployment, invokes fetcher to copy the function into the pod, and
hits the the specialize endpoint on the environment container. This
causes the function to be loaded. The pod is now specific to that
function.
This function pod is cached; it's cleaned up if it's unused for a few
minutes.
Router
------
- Cache trigger -> container instance mapping; implement cache miss and expiration.
The router forwards HTTP requests to function pods. If there's no
running service for a function, it requests one from poolmgr, while
holding on to the request; when the function's service is ready it
forwards the request.
- Invoke Specializer, setup up k8s API
The router is stateless and can be scaled up if needed, according to
load.
- Forward requests
Kubewatcher
-----------
The Router is stateless -- it can be scaled or killed at any time.
Kubewatcher watches the Kubernetes API and invokes functions
associated with watches, sending the watch event to the function.
There's a lot of functionality overlap with K8S Ingress Controllers.
We should clearly use Ingress and Ingress Controllers in some way.
It's not exactly clear at the moment how -- should make a whole new
Ingress Controller perhaps based on the contrib/nginx
The controller keeps track of user's requested watches and associated
functions. Kubewatcher watches the API based on these requests; when
a watch event occurs, it serializes the object and calls the function
via the router.
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.
Autoscaler
----------
Message Queue Trigger
---------------------
This autoscales the language run containers that are backing a
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.
What metrics this is based on is TBD.
Here's a diagram of the components:
- Number of requests/sec
- "Backlog" -- number of outstanding requests not yet started -- how to measure this?
- Change in turn-around time?
![Message queue trigger Diagram](https://user-images.githubusercontent.com/202578/27012344-9457cb24-4f00-11e7-8d6b-926ff01637b3.jpg)
Environment Container
---------------------
Language Build Container
------------------------
Environment containers run user-defined functions. Environment
containers are language specific. They must contain an HTTP server
and a loader for functions.
* The Language Build Container is a container that is invoked for a
build. It takes one user-created function and outputs something
that can be run by the corresponding Language Run Container.
Poolmgr deploys the environment container into a pod with fetcher
(fetcher is a simple utility that can fetch an HTTP url to a file at a
configured location). This pod forms a "generic pod", because it can
be loaded with any function.
* The Build Container must implement the Language Build Container
interface.
When poolmgr needs to create a service for a function, it calls
fetcher to fetch the function. Fetcher downloads the function into a
volume shared between fetcher and this environment container. Poolmgr
then requests the container to load the function.
Logger
------
Language Run Container
----------------------
* The Language Run Container is the container in which user functions
run.
* The Run Container is started without the user function. It must
start as a "Generic Container". It must implement the
"specialization interface". In short, it must implement an HTTP
server that can receive a piece of code, verify its signature, and
map it to an HTTP endpoint. See
Documentation/specs/LanguageRunContainerSpec.md for details.
Logger helps to forward function logs to centralized db service for log
persistence. Currently only influxdb is supported to store logs.
Following is a diagram describe how log service works:
![Logger Diagram](https://cloud.githubusercontent.com/assets/202578/23100399/b0e3ea00-f6ba-11e6-8f2f-6588cfef2e84.png)
1. Pool manager choose a pod from pool to execute user function
2. Pool manager makes a HTTP POST to logger helper, once helper receives
the request it creates a symlink to container log for fluentd.
3. Fluentd reads log from symlink and pipes to influxdb
4. `fission function logs ...` retrieve event logs from influxdb with
optional log filter
5. Pool manager removes function pod from pool
6. Pool manager asks logger helper to stop piping logs, logger removes symlink.
-71
View File
@@ -1,71 +0,0 @@
Programming Model
=================
This document describes the programming model for fission functions.
See TERMINOLOGY for definitions of terms, such as _function_,
_instance_, and _trigger_.
Idempotency
===========
Fission assumes that functions are idempotent. Functions whose instances
die without a result are restarted, upto a certain restart limit.
Time Limits
===========
By default, there is no time limit on fission functions.
Idle running instances may be killed at any time (usually after the
default idle timeout of 10 minutes, but this is configurable).
Mapping
=======
This section specifies the mapping between: (1) HTTP and other
triggers, and (2) function parameters, return values, and exceptions.
In other words, this section describes how a function should be called
based on a given trigger, and how the functions behaviour affects the
result returned from the trigger.
The HTTP Trigger
----------------
NodeJS
------
NodeJS functions are called with a context object.
context.request contains the nodeJS Request object.
In addition,
context.queryString contains the parsed querystring
context.body contains the parsed body
context.status sets the HTTP response status code. If context.status
is an invalid HTTP status, then the HTTP status is set to 500.
Exceptions result in a HTTP 500 error.
XXX Should we have a "context.done()"? What are the pros and cons?
XXX Who handles serialization and deserialization? Should we do json
automatically based on content-type and accept headers?
Python
------
Same as node, pretty much.
+106
View File
@@ -0,0 +1,106 @@
# Fission Roadmap
## Function features ([area-func](https://github.com/fission/fission/labels/area-func))
- Secrets, configmaps, env vars
- Volumes
- Mem requests and limits
- Function exec time deadline
- Expose a regular K8s Service for a function
## Fission API ([area-api](https://github.com/fission/fission/labels/area-api))
- TPR-based controller
- API authentication
- Aggregated API server
## Development Workflows ([area-dev](https://github.com/fission/fission/labels/area-dev))
- Function Versioning
- Versioning for a group of functions
- Rolling upgrades
- for one function
- for multiple functions
- for functions + other kubernetes deployments
- Unit testing
## API Gateway / Ingress features ([area-ingress](https://github.com/fission/fission/labels/area-ingress))
- Function authn hooks
- K8s Ingress flag
- Bundle an ingress controller?
## Workflows and Function composition ([area-composition](https://github.com/fission/fission/labels/area-composition))
- Simple Composition - Sync
- Simple Composition - Async
- Hooks (pre, post, on-error)
- Workflows
- Testing in the presence of function composition
## Events ([area-events](https://github.com/fission/fission/labels/area-events))
- NATS
- Kafka
- AWS: SNS, SQS
- Google PubSub
- RabbitMQ
- Other event sources
- Bundle an event queue (NATS streaming, most probably)
## Operability ([area-ops](https://github.com/fission/fission/labels/area-ops))
### Fission Install/Upgrade ([area-install](https://github.com/fission/fission/labels/area-install))
- Helm Installer for Fission
- Helm installation for fission functions
- CLI installer/upgrader? ("fission upgrade")
- Upgrade checker/reminder (like minikube does)
- CLI auto-upgrader
### Function observation ([area-observe](https://github.com/fission/fission/labels/area-observe))
- Function Logging
- Tracing -- Opentracing
- Metrics -- Prometheus
- Function exception tracking
- Logging function load errors
- Tracing fission overheads for each function
## Function Security
- Function isolation (is authz hook sufficient or do we need something like mutual TLS?)
- Function service accounts
## UX, especially for beginners ([area-ux](https://github.com/fission/fission/labels/area-ux))
- Fission CLI should include a tutorial
- Fission CLI should have a way to drop you into the UI
- Eliminate FISSION_URL, just use kube client to find fission url. Also useful to grab credentials.
## Documentation ([area-doc](https://github.com/fission/fission/labels/area-doc))
- Installation guide improvements
- Troubleshooting guide for common problems
- FAQ
- Performance overview
- Render docs nicely to fission.io
## Web UI (tracked separately in the fission-ui repo)
## Performance and Scalability ([area-perf](https://github.com/fission/fission/labels/area-perf))
- Autoscaling
- Cold-start optimization -- optimistically choose from pool, save about ~20msec
- Cold-start optimization -- preload funcs in fetcher
- Cold-start optimization -- preload libraries in envs (v2) -- mem vs. speed tradeoff
## Function extensibility ([area-ext](https://github.com/fission/fission/labels/area-ext))
- Env v2: easy addition of dependencies etc.
- Integration with Service Broker
## Multi-area stuff
- Execution strategies: cold-start pool vs create-pod-on-cold-start -- one size doesn't fit all, at least with current tech; abstract over execution strategies according to requirements
+11 -18
View File
@@ -1,22 +1,15 @@
_Function_: A _function_ is the smallest unit of a program in Fission.
It's a program with an function as an entry point -- it doesn't have
to be just one function.
_Function_: A fission function is something that's mapped to a
_trigger_ and run on demand. Though we call it a "function", this is
a bit imprecise, since it's actually a module with an function as an
entry point -- it doesn't have to be just one function.
_Trigger_: Triggers are what cause functions to be called. For
example the HTTP trigger causes functions to be called on HTTP
requests.
example, an HTTP trigger causes functions to be called on HTTP
requests. Kubernetes Watch triggers cause functions to be called when
a Kubernetes watch changes. Future triggers will include message
queues, timers, storage systems, etc.
_Instance_: Fission creates and runs servers containing these
functions. The running server is called an _instance_. You normally
don't need to worry about instances: their management is completely
automatic and transparent to you. However, certain configuration
settings allow you to tweak the behaviour of instances -- for example,
you can configure the strategy for destroying unused instances.
_Generic Instance_: An instance with no user code in it. The process
of including the user code to a Generic Instance is _Specialization_,
and the resulting instance is a _Specialized Instance_.
_Specialized Instance_: See _General Instance_.
_Environments_: Environments are the language-specific parts of
Fission. Environment containers wrap the user's function and present
a common interface to the rest of the fission framework.
-15
View File
@@ -1,15 +0,0 @@
Fission is functions-as-a-service for Kubernetes.
You write some functions, and setup some way for them to be invoked
("triggers"). Triggers can be HTTP requests (HTTP Triggers) or timers
(Time Triggers).
Topics
======
Testing
-------
Function Updates
----------------
+24
View File
@@ -0,0 +1,24 @@
# Programming Model
This document describes the programming model for fission functions.
## Time Limits
By default, there is no time limit on fission functions.
Idle running instances may be killed at any time (usually after the
default idle timeout of 10 minutes, but this is configurable).
## HTTP Triggers
Functions triggered over HTTP receive the HTTP request object in the
context. The request's query string, POST body, etc. can be retrieved
from this object. The interface is language-specific: see [TODO] for
documentation on the context object in each environment.
## Kubernetes Watch Event Triggers
Kubernetes watches can be used to trigger functions. These functions
receive the Kubernetes watch.Event object in JSON-serialized form.
+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.
+327
View File
@@ -0,0 +1,327 @@
# Fission Environments Redesign
As Fission supports more languages and reaches a wider set of use
cases, it's time to ask how well the current Environments design is
holding up.
## Environments V1: What we learned
Environments V1 is very simple idea: an environments is one Docker
image with an HTTP server + dynamic loader for that language; it's run
in a pod with a language-agnostic sidecar (fetcher) that downloads and
saves the function into a volume shared with the language-specific
container.
### Pros:
* Abstracted away images.
* Very fast cold start
* No image registry to manage (neither for the user nor for fission
implementation)
* Relatively small amount of language specific code. (python env is <
100 lines)
### Cons:
* Doesn't work well for compiled languages
* Users have to rebuild the image to add dependencies
* Only one file supported
* Errors in loading are not surfaced properly. It is especially
annoying to wait until runtime to see a syntax error that could have
been caught on function upload.
* Starting a Pod without knowing the functions has its limitations: we
can't set CPU/memory limits, we can't mount volumes (persistent
volumes, secrets, configmaps). We also can't change the namespace
the Pod is in.
* Not great for a large code base
* Some people want to operate at the image level but still get the
on-demand execution semantics of FaaS. This is a cost-optimization
use case.
### Discussion
Early feedback shows that almost evey user ends up rebuilding images
to add some dependecies. Some sort of automated dependecy resolution
would be very nice to have and improve the development workflow. In
other words, just attach a package.json(nodejs) or
requirements.txt(python) with a function, and fission will do the
rest. There's also the possiblity of supporting buildpacks (simple
zipfiles), a la AWS Lambda.
Though we can support compiled languages by doing the compilation
inside the cold-start, that's not a great solution because: (a)
compile errors would be reported at runtime, and (b) because the
overhead of compilation doesn't really need to be inside the
cold-start latency.
Non-trivial functions will need multiple files. That also helps for
common code across functions. So we need a way for the user to define
a function as a collection of code with an entry point.
Finally, Docker images remain the most flexible way to package an app.
Today, users can always rebuild an environment image to include
anything they want. But those images must still run a server that
implements fission-environment interface (i.e. the specialize
endpoint). So perhaps there could be a way for users to say "don't
use environments, I've already packaged up my function, here it is".
## Environment V2 Requirements
Roughly in order of priority:
0. Retain the simplicity of the simple use cases. First user
experience shoud remain trivial -- write a function, map a URL,
done.
1. Support compiled languages. Support error reporting on function
upload rather than cold start.
2. Support functions as a collection of files rather than just one
file.
3. Support automated environment-specific dependecy resolution.
(#3 may end up having the same solution as #1. You could think of
gathering deps as a "compilation" of package.json,
requirements.txt, etc.)
4. Support functions as images.
### User stories
#### Environment Creation
V1 Environments were just an image. V2 Environments will be a yaml
file with the following properties:
* Run time image (required)
* Version (required)
* Builder image (optional)
* Build invocation command (required if builder image specified)
* File name extension(s) (optional)
The version will be used to distinguish V2 environments from V1.
```
$ cat golang.yaml
type: Environment
metadata:
name: go
spec:
runtimeImage: fission/go-runtime
builderImage: fission/go-builder
buildCommand:
- "/build.sh"
fileExtentions:
- go
$ fission env create -f golang.yaml
```
#### Function creation for compiled languages
User writes a function in a compiled language, for example Go.
```
$ fission function create --code blah.go
<compilation errors>
<user edits file>
$ $EDITOR blah.go
<fixes errors>
$ fission function update --code blah.go
<success>
$ fission route ... # routes work as usual
```
This same user story applies to interpreted languages too, where the
"compilation" step can be used to check for syntax errors.
#### Compiled language, without using fission builds
User compiles their function locally, resulting in a set of one or
more binaries. The user packages these up as a zip file, creating a
"deployment package".
```
$ fission function create --deployment-package foo.zip
$ fission route ... # routes work as usual
```
In this use case, fission is no longer operating at the source
level. Builds are left to the user and fission only sees the
deployment package package.
#### Collections of source files
The user can create a source package -- a set of source files in a
zip.
```
$ fission function create --source-package foo.zip
```
This workflow works similarly to providing a single source file.
In addition, fission CLI could support automatic creation of source packages, e.g.
```
$ fission function create --source-files *.js
```
This is purely client-side "syntactic sugar" -- the CLI creates the
source package instead of the user having to do it manually. It
doesn't change semantics; the source package is still handled as one
object.
#### Handling Dependencies
The source package of a function can contain dependency specs.
Fission framework proper does not treat this spec in any special way;
it's just another file in the source package. These will be
interpreted by the environment builder.
```
$ fission function create --source-files *.js --source-files package.json
```
In this case, the CLI will create a source package containing the JS
files and package.json. The NodeJS environment builder will create a
deployment package out of these files. The runtime environment will
load and run the deployment package.
#### V1 Compatibility
V1 Environments will continue to be supported. Existing commands will
continue to work. V1 environments won't support newer features like
builds, source and deployment packages, etc.
### Implementation
#### Environment Type
The environment type has a set of new properties: version, runtime
image, builder image, build command, file extension(s).
#### Function Type
The function type has new properties: source package, deployment
package. The literal code string continues to be supported, but it
will have a specified size limit, say 512KB.
#### Source and Binary Packages
A package is just a zip file. It's contents are opaque to fission:
the meaning of its contents is defined by the environment. Fission's
job is to manage the storage and delivery of the package into build
and runtime environments.
#### Storage Service
The storage service will have an HTTP API to upload and download
files. It can store the packages on a persistent volume or as objects
in cloud storage services such as S3.
Storage service has a garbage collection API endpoint. When invoked,
it will remove all packages that are not referenced from any function.
#### Fetcher
Fetcher gets some new responsibilities:
1. It must now also handle zip/unzip of packages
2. It must know how to upload to the storage service (so it's not
exactly "fetcher" any more, but...)
#### Runtime Environment Interface
The V2 runtime environment interface is very similar to V1
environments. Environments must support a dynamic loader and have an
HTTP server that forwards requests to the loaded module.
The differences:
* V2 runtimes must support loading a deployment package. Fetcher is
responsible for unzipping a deployment package, but interpretation
of the contents is up to the environment's code. For example, it
may have to include the directory where the deployment package is
unzipped in its module load path.
[TODO any other differences?]
#### Buildmgr
A new service that will manage builds. Its design is similar to
poolmgr, except it is triggered on creation or update of a function,
rather than HTTP requests.
Buildmgr creates a builder deployment+service for each environment.
Pods in this deployment run the environment's build container, and
fetcher, with a shared volume between the two containers.
When a function is created or updated with a source package, buildmgr
notices this and triggers a build. First, it calls fetcher to
download the source package into a shared volume with the build
container. It then invokes the builder by running the build
invocation command in the build container. Next, it calls fetcher to
package up the output of the builder and store the built package into
the the storage service.
Finally, it updates the function object in the controller API with a
reference to the built package.
[We can collapse this workflow into one request into the builder
service, which would make it easier to scale up the builder
deployment; if we used multiple requests we'd need some sort of
affinity rule, but k8s services only support IP based affinity.]
#### Poolmgr
Poolmgr remains relatively unchanged. Instead of contructing URLs for
function metadata, it uses the deployment package URL in the function
object.
#### CLI
Client libraries and CLI have to deal with the new properties in
functions and environments.
The CLI will now talk to both storage service and controller. When a
function is created, the user can specify the function in one of 3
ways:
1. One source file, same as v1.
2. A source package (or a set of source files, which is turned into a
source package by the CLI)
3. A deployment package
If the file is specified as a source file, fission CLI will use the
code literal if it's under the size limit; otherwise it should use the
storage service. This will allow users to use fission deployments
with no storage service, but with a size limit on functions.
For the case of a source or deployment package, the CLI first does an
upload to the storage service, then creates a function object with a
reference to the uploaded package.
+221
View File
@@ -0,0 +1,221 @@
- [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
### 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 https://github.com/fission/fission/releases/download/nightly20170621/fission-rbac.yaml
$ kubectl create -f https://github.com/fission/fission/releases/download/nightly20170621/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 https://github.com/fission/fission/releases/download/nightly20170621/fission-rbac.yaml
$ kubectl create -f https://github.com/fission/fission/releases/download/nightly20170621/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 https://github.com/fission/fission/releases/download/nightly20170621/fission-openshift.yaml
$ oc create -f https://github.com/fission/fission/releases/download/nightly20170621/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/nightly20170621/fission-openshift.yaml
$ oc create -f https://github.com/fission/fission/releases/download/nightly20170621/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 -Lo fission https://github.com/fission/fission/releases/download/nightly20170621/fission-cli-osx && chmod +x fission && sudo mv fission /usr/local/bin/
```
#### Linux
```
$ curl -Lo fission https://github.com/fission/fission/releases/download/nightly20170621/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/nightly20170621/fission-cli-windows.exe)
### 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!
```
### Enable Persistent Function Logs (Optional)
Fission uses InfluxDB to store logs and fluentd to forward them from
function pods into InfluxDB.
```
$ kubectl create -f fission-logger.yaml
```
That's it for the basic setup. You can now use following command to view function logs:
```
$ fission function logs --name hello
```
You can also list the all the pods that have hosted the function
(including ones that aren't alive any more) and view logs for a
particular pod:
```
$ fission function pods --name hello
$ fission function logs --name hello --pod <pod name>
```
### Use the web based Fission-ui (Optional)
[Fission-ui](https://github.com/fission/fission-ui) is the ui for fission maintained by the community.
It allows users to observe and manage fission. It also provides a simple online development environment for serverless functions.
To setup Fission-ui with fission in k8s is simple:
```bash
# 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 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"
```
+76 -53
View File
@@ -1,79 +1,102 @@
Fission
=======
Fission: Serverless Functions for Kubernetes
============================================
[![Build Status](https://travis-ci.org/fission/fission.svg?branch=master)](https://travis-ci.org/fission/fission)
[![Go Report Card](https://goreportcard.com/badge/github.com/fission/fission)](https://goreportcard.com/report/github.com/fission/fission)
[![Fission Slack](http://slack.fission.io/badge.svg)](http://slack.fission.io)
[fission.io](http://fission.io) [@fissionio](http://twitter.com/fissionio)
Fission is a fast serverless framework for Kubernetes with a focus on
developer productivity and high performance.
It's customizable (with sensible defaults), extensible to any
language, and interoperates well with other infrastructure.
Fission operates on _just the code_: Docker and Kubernetes are
abstracted away under normal operation, though you can use both to
extend Fission if you want to.
See https://fission.io for more.
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.
### Performance: 100msec cold start
Fission maintains a pool of "warm" containers that each contain a
small dynamic loader. When a function is first called,
i.e. "cold-started", a running container is chosen and the function is
loaded. This pool is what makes Fission fast: cold-start latencies
are typically about 100msec.
### Kubernetes is the right place for Serverless
We're built on Kubernetes because we think any non-trivial app will
use a combination of serverless functions and more conventional
microservices, and Kubernetes is a great framework to bring these
together seamlessly.
Building on Kubernetes also means that anything you do for operations
on your Kubernetes cluster &mdash; such as monitoring or log
aggregation &mdash; also helps with ops on your Fission deployment.
Running Fission
===============
Fission Concepts
----------------
On your own Kubernetes cluster
------------------------------
A _function_ is a piece of code that follows the fission function
interface.
### Setup Kubernetes
An _environment_ contains the language- and runtime-specific parts of
running a function. Fission comes with NodeJS and Python
environments; you can also extend environments or create entirely new
ones if you want. (An environment is essentially just a container
with a webserver and dynamic loader.)
On your laptop:
A _trigger_ is something that maps an event to a function; Fission
supports HTTP routes as triggers today, with upcoming support for
other types of event triggers, such as timers and Kubernetes events.
https://github.com/kubernetes/minikube
Usage
-----
Or use Google Container Engine.
### Verify access to the cluster
$ kubectl version
### Get and Run Fission
If you're using GKE, use the fission.yaml unmodified. If you're using
minikube, change all instances of LoadBalancer services to NodePort.
$ curl http://fission.io/fission.yaml | kubectl create -f -
$ kubectl --namespace fission get services
Save the external IP addresses of controller and router services in
FISSION_URL and FISSION_ROUTER, respectively.
### Install the client CLI
$ curl http://fission.io/fission > fission
$ chmod +x fission
$ sudo mv fission /usr/local/bin/
### Run an example
```bash
# Add the stock NodeJS env to your Fission deployment
$ fission env create --name nodejs --image fission/node-env
$ echo 'module.exports = function(context, callback) { callback(200, "Hello, world!\n"); }' > hello.js
# A javascript one-liner that prints "hello world"
$ curl https://raw.githubusercontent.com/fission/fission/master/examples/nodejs/hello.js > hello.js
# Upload your function code to fission
$ fission function create --name hello --env nodejs --code hello.js
# Map GET /hello to your new function
$ fission route create --method GET --url /hello --function hello
# Run the function. This takes about 100msec the first time.
$ curl http://$FISSION_ROUTER/hello
Hello, world!
```
See the [examples](examples) directory for more.
Running Fission on your Cluster
===============================
See the [installation guide](INSTALL.md).
Compiling Fission
=================
See the [compilation guide](Compiling.md).
Status
======
Fission is in early alpha. Watch this space for announcements soon!
Fission is in early alpha. It's not suitable for production use just
yet.
Performance
===========
The alpha release has focussed on cold-start latency performance. For
requests that don't have a running instance, i.e. a "cold start",
fission has a latency overhead of less than 100 msec for the NodeJS
environment.
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
[twitter](https://twitter.com/fissionio).
+3 -3
View File
@@ -17,11 +17,11 @@ limitations under the License.
package cache
import (
"time"
"errors"
"fmt"
"github.com/platform9/fission"
"time"
"github.com/fission/fission"
)
type requestType int
+9 -7
View File
@@ -16,9 +16,11 @@ limitations under the License.
package cache
import "testing"
import "log"
import "time"
import (
"log"
"testing"
"time"
)
func checkErr(err error) {
if err != nil {
@@ -27,11 +29,11 @@ func checkErr(err error) {
}
func TestCache(t *testing.T) {
c := MakeCache(100 * time.Millisecond)
c := MakeCache(100*time.Millisecond, 100*time.Millisecond)
err := c.Set("a", "b")
err, _ := c.Set("a", "b")
checkErr(err)
err = c.Set("p", "q")
err, _ = c.Set("p", "q")
checkErr(err)
val, err := c.Get("a")
@@ -53,7 +55,7 @@ func TestCache(t *testing.T) {
log.Panicf("found deleted element")
}
err = c.Set("expires", "42")
err, _ = c.Set("expires", "42")
checkErr(err)
time.Sleep(150 * time.Millisecond)
_, err = c.Get("expires")
+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
+12
View File
@@ -0,0 +1,12 @@
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
+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.
+58
View File
@@ -0,0 +1,58 @@
# 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
```
+37
View File
@@ -0,0 +1,37 @@
1. Install the client CLI.
Mac:
$ curl http://fission.io/mac/fission > fission && 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/
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!
+16
View File
@@ -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 -}}
+141
View File
@@ -0,0 +1,141 @@
---
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
+32
View File
@@ -0,0 +1,32 @@
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
+23
View File
@@ -0,0 +1,23 @@
#
# Fission chart configuration
#
## Kubernetes configuration
## For minikube, set this to NodePort, elsewhere use LoadBalancer.
serviceType: LoadBalancer
## Fission image repostiroy
image: fission/fission-bundle
## Fission image version
imageTag: alpha20170124
## 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
+30
View File
@@ -0,0 +1,30 @@
/*
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 fission
import (
"fmt"
)
func UrlForFunction(m *Metadata) 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)
}
}
+71 -7
View File
@@ -21,21 +21,47 @@ import (
"net/http"
"os"
"runtime/debug"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"github.com/platform9/fission"
"github.com/fission/fission"
"github.com/fission/fission/fission/logdb"
)
type API struct {
FunctionStore
HTTPTriggerStore
EnvironmentStore
type (
API struct {
FunctionStore
HTTPTriggerStore
TimeTriggerStore
MessageQueueTriggerStore
EnvironmentStore
WatchStore
}
logDBConfig struct {
httpURL string
username string
password string
}
)
func MakeAPI(rs *ResourceStore) *API {
api := &API{
FunctionStore: FunctionStore{ResourceStore: *rs},
HTTPTriggerStore: HTTPTriggerStore{ResourceStore: *rs},
TimeTriggerStore: TimeTriggerStore{ResourceStore: *rs},
MessageQueueTriggerStore: MessageQueueTriggerStore{ResourceStore: *rs},
EnvironmentStore: EnvironmentStore{ResourceStore: *rs},
WatchStore: WatchStore{ResourceStore: *rs},
}
return api
}
func (api *API) respondWithSuccess(w http.ResponseWriter, resp []byte) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
_, err := w.Write(resp)
if err != nil {
// this will probably fail too, but try anyway
@@ -50,8 +76,26 @@ func (api *API) respondWithError(w http.ResponseWriter, err error) {
http.Error(w, msg, code)
}
func (api *API) getLogDBConfig(dbType string) logDBConfig {
dbType = strings.ToUpper(dbType)
// retrieve db auth config from the env
url := os.Getenv(fmt.Sprintf("%s_URL", dbType))
if url == "" {
// set up default database url
url = logdb.INFLUXDB_URL
}
username := os.Getenv(fmt.Sprintf("%s_USERNAME", dbType))
password := os.Getenv(fmt.Sprintf("%s_PASSWORD", dbType))
return logDBConfig{
httpURL: url,
username: username,
password: password,
}
}
func (api *API) HomeHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "{message: \"Fission API\", version: \"0.1.0\"}\n")
w.Header().Set("Content-Type", "application/json; charset=utf-8")
fmt.Fprintf(w, "{\"message\": \"Fission API\", \"version\": \"0.1.0\"}\n")
}
func (api *API) Serve(port int) {
@@ -76,6 +120,26 @@ func (api *API) Serve(port int) {
r.HandleFunc("/v1/environments/{environment}", api.EnvironmentApiUpdate).Methods("PUT")
r.HandleFunc("/v1/environments/{environment}", api.EnvironmentApiDelete).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("/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("/v1/triggers/messagequeue", api.MessageQueueTriggerApiList).Methods("GET")
r.HandleFunc("/v1/triggers/messagequeue", api.MessageQueueApiCreate).Methods("POST")
r.HandleFunc("/v1/triggers/messagequeue/{mqTrigger}", api.MessageQueueApiGet).Methods("GET")
r.HandleFunc("/v1/triggers/messagequeue/{mqTrigger}", api.MessageQueueApiUpdate).Methods("PUT")
r.HandleFunc("/v1/triggers/messagequeue/{mqTrigger}", api.MessageQueueApiDelete).Methods("DELETE")
r.HandleFunc("/proxy/{dbType}", api.FunctionLogsApiPost).Methods("POST")
address := fmt.Sprintf(":%v", port)
log.WithFields(log.Fields{"port": port}).Info("Server started")
+215 -8
View File
@@ -24,18 +24,39 @@ import (
"testing"
"time"
log "github.com/Sirupsen/logrus"
etcdClient "github.com/coreos/etcd/client"
log "github.com/sirupsen/logrus"
"golang.org/x/net/context"
"github.com/platform9/fission"
"github.com/platform9/fission/controller/client"
"github.com/fission/fission"
"github.com/fission/fission/controller/client"
)
var g struct {
client *client.Client
}
func assertNameReuseFails(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) {
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")
}
func assertCronSpecFails(err error) {
assert(err != nil, "using an invalid cron spec must fail")
fe, ok := err.(fission.Error)
assert(ok, "error must be a fission Error")
assert(fe.Code == fission.ErrorInvalidArgument, "error must be a invalid argument error")
}
func TestFunctionApi(t *testing.T) {
log.SetFormatter(&log.TextFormatter{DisableColors: true})
@@ -50,12 +71,17 @@ func TestFunctionApi(t *testing.T) {
},
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.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")
@@ -101,12 +127,81 @@ func TestFunctionApi(t *testing.T) {
assert(len(funcs) == 2,
"created two functions, but didn't find them")
funcs_url := g.client.Url + "/v1/functions"
resp, err := http.Get(funcs_url)
panicIf(err)
defer resp.Body.Close()
assert(resp.StatusCode == 200, "http get status code on /v1/functions")
var found bool = false
for _, b := range resp.Header["Content-Type"] {
if b == "application/json; charset=utf-8" {
found = true
}
}
assert(found, "incorrect response content type")
err = g.client.FunctionDelete(&fission.Metadata{Name: "foo"})
panicIf(err)
err = g.client.FunctionDelete(&fission.Metadata{Name: "bar"})
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{
@@ -119,10 +214,16 @@ func TestHTTPTriggerApi(t *testing.T) {
Uid: "",
},
}
_, err := g.client.HTTPTriggerGet(&fission.Metadata{Name: "foo"})
assertNotFoundFails(err, "trigger")
m, err := g.client.HTTPTriggerCreate(testTrigger)
panicIf(err)
defer g.client.HTTPTriggerDelete(m)
_, err = g.client.HTTPTriggerCreate(testTrigger)
assertNameReuseFails(err, "trigger")
tr, err := g.client.HTTPTriggerGet(m)
panicIf(err)
testTrigger.Metadata.Uid = m.Uid
@@ -140,6 +241,10 @@ func TestHTTPTriggerApi(t *testing.T) {
testTrigger.Metadata.Name = "yyy"
m, err = g.client.HTTPTriggerCreate(testTrigger)
assert(err != nil, "duplicate trigger should not be allowed")
testTrigger.UrlPattern = "/hi2"
m, err = g.client.HTTPTriggerCreate(testTrigger)
panicIf(err)
defer g.client.HTTPTriggerDelete(m)
@@ -156,10 +261,16 @@ func TestEnvironmentApi(t *testing.T) {
},
RunContainerImageUrl: "gcr.io/xyz",
}
_, err := g.client.EnvironmentGet(&fission.Metadata{Name: "foo"})
assertNotFoundFails(err, "environment")
m, err := g.client.EnvironmentCreate(testEnv)
panicIf(err)
defer g.client.EnvironmentDelete(m)
_, err = g.client.EnvironmentCreate(testEnv)
assertNameReuseFails(err, "environment")
tr, err := g.client.EnvironmentGet(m)
panicIf(err)
testEnv.Metadata.Uid = m.Uid
@@ -185,22 +296,109 @@ func TestEnvironmentApi(t *testing.T) {
assert(len(ts) == 2, "created two envs, but didn't find them")
}
func TestWatchApi(t *testing.T) {
testWatch := &fission.Watch{
Metadata: fission.Metadata{
Name: "xxx",
Uid: "yyy",
},
Namespace: "default",
ObjType: "pod",
LabelSelector: "",
FieldSelector: "",
Function: fission.Metadata{
Name: "foo",
Uid: "",
},
Target: "",
}
_, err := g.client.WatchGet(&fission.Metadata{Name: "foo"})
assertNotFoundFails(err, "watch")
m, err := g.client.WatchCreate(testWatch)
panicIf(err)
defer g.client.WatchDelete(m)
_, err = g.client.WatchCreate(testWatch)
assertNameReuseFails(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")
testWatch.Metadata.Name = "yyy"
m2, err := g.client.WatchCreate(testWatch)
panicIf(err)
defer g.client.WatchDelete(m2)
ws, err := g.client.WatchList()
panicIf(err)
assert(len(ws) == 2, "created two envs, but didn't find them")
}
func TestTimeTriggerApi(t *testing.T) {
testTrigger := &fission.TimeTrigger{
Metadata: fission.Metadata{
Name: "xxx",
Uid: "yyy",
},
Cron: "0 30 * * * *",
Function: fission.Metadata{
Name: "foo",
Uid: "",
},
}
_, err := g.client.TimeTriggerGet(&fission.Metadata{Name: "foo"})
assertNotFoundFails(err, "trigger")
m, err := g.client.TimeTriggerCreate(testTrigger)
panicIf(err)
defer g.client.TimeTriggerDelete(m)
_, err = g.client.TimeTriggerCreate(testTrigger)
assertNameReuseFails(err, "trigger")
tr, err := g.client.TimeTriggerGet(m)
panicIf(err)
testTrigger.Metadata.Uid = m.Uid
assert(*testTrigger == *tr, "trigger should match after reading")
testTrigger.Cron = "@hourly"
m2, 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)
assertCronSpecFails(err)
ts, err := g.client.TimeTriggerList()
panicIf(err)
assert(len(ts) == 1, "created one trigger, but didn't find it")
}
func TestMain(m *testing.M) {
flag.Parse()
fileStore, ks, rs := getTestResourceStore()
defer os.RemoveAll(fileStore.root)
api := &API{
FunctionStore: FunctionStore{ResourceStore: *rs},
HTTPTriggerStore: HTTPTriggerStore{ResourceStore: *rs},
EnvironmentStore: EnvironmentStore{ResourceStore: *rs},
}
api := MakeAPI(rs)
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)
@@ -208,6 +406,15 @@ func TestMain(m *testing.M) {
resp, err := http.Get("http://localhost:8888/")
panicIf(err)
assert(resp.StatusCode == 200, "http get status code on root")
var found bool = false
for _, b := range resp.Header["Content-Type"] {
if b == "application/json; charset=utf-8" {
found = true
}
}
assert(found, "incorrect response content type")
_, err = ioutil.ReadAll(resp.Body)
panicIf(err)
+325 -31
View File
@@ -26,9 +26,7 @@ import (
"net/http"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/platform9/fission"
"github.com/fission/fission"
)
type (
@@ -80,19 +78,15 @@ func (c *Client) url(relativeUrl string) string {
func (c *Client) handleResponse(resp *http.Response) ([]byte, error) {
if resp.StatusCode != 200 {
var errCode int
switch resp.StatusCode {
case 403:
errCode = fission.ErrorNotAuthorized
case 404:
errCode = fission.ErrorNotFound
case 400:
errCode = fission.ErrorInvalidArgument
default:
errCode = fission.ErrorInternal
}
return nil, fission.MakeError(errCode,
fmt.Sprintf("HTTP error %v", resp.StatusCode))
return nil, fission.MakeErrorFromHTTP(resp)
}
body, err := ioutil.ReadAll(resp.Body)
return body, err
}
func (c *Client) handleCreateResponse(resp *http.Response) ([]byte, error) {
if resp.StatusCode != 201 {
return nil, fission.MakeErrorFromHTTP(resp)
}
body, err := ioutil.ReadAll(resp.Body)
return body, err
@@ -114,12 +108,8 @@ func (c *Client) FunctionCreate(f *fission.Function) (*fission.Metadata, error)
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
body, err := c.handleCreateResponse(resp)
if err != nil {
log.WithFields(log.Fields{
"name": f.Metadata.Name,
"err": err,
}).Error("Failed to create function")
return nil, err
}
@@ -223,6 +213,7 @@ func (c *Client) FunctionList() ([]fission.Function, error) {
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
@@ -250,12 +241,8 @@ func (c *Client) HTTPTriggerCreate(t *fission.HTTPTrigger) (*fission.Metadata, e
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
body, err := c.handleCreateResponse(resp)
if err != nil {
log.WithFields(log.Fields{
"name": t.Metadata.Name,
"err": err,
}).Error("Failed to create http trigger")
return nil, err
}
@@ -334,6 +321,7 @@ func (c *Client) HTTPTriggerList() ([]fission.HTTPTrigger, error) {
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
@@ -361,12 +349,8 @@ func (c *Client) EnvironmentCreate(env *fission.Environment) (*fission.Metadata,
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
body, err := c.handleCreateResponse(resp)
if err != nil {
log.WithFields(log.Fields{
"name": env.Metadata.Name,
"err": err,
}).Error("Failed to create environment")
return nil, err
}
@@ -445,6 +429,7 @@ func (c *Client) EnvironmentList() ([]fission.Environment, error) {
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
@@ -459,3 +444,312 @@ func (c *Client) EnvironmentList() ([]fission.Environment, error) {
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
}
func (c *Client) MessageQueueTriggerCreate(t *fission.MessageQueueTrigger) (*fission.Metadata, 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 fission.Metadata
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) MessageQueueTriggerGet(m *fission.Metadata) (*fission.MessageQueueTrigger, error) {
relativeUrl := fmt.Sprintf("triggers/messagequeue/%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.MessageQueueTrigger
err = json.Unmarshal(body, &t)
if err != nil {
return nil, err
}
return &t, nil
}
func (c *Client) MessageQueueTriggerUpdate(mqTrigger *fission.MessageQueueTrigger) (*fission.Metadata, 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 fission.Metadata
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) MessageQueueTriggerDelete(m *fission.Metadata) error {
relativeUrl := fmt.Sprintf("triggers/messagequeue/%v", m.Name)
if len(m.Uid) > 0 {
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
}
err := c.delete(relativeUrl)
return err
}
func (c *Client) MessageQueueTriggerList(mqType string) ([]fission.MessageQueueTrigger, error) {
relativeUrl := "triggers/messagequeue"
if len(mqType) > 0 {
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([]fission.MessageQueueTrigger, 0)
err = json.Unmarshal(body, &triggers)
if err != nil {
return nil, err
}
return triggers, nil
}
+5 -3
View File
@@ -21,10 +21,10 @@ import (
"io/ioutil"
"net/http"
log "github.com/Sirupsen/logrus"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"github.com/platform9/fission"
"github.com/fission/fission"
)
func (api *API) EnvironmentApiList(w http.ResponseWriter, r *http.Request) {
@@ -47,8 +47,8 @@ func (api *API) EnvironmentApiCreate(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
api.respondWithError(w, err)
return
}
defer r.Body.Close()
var env fission.Environment
err = json.Unmarshal(body, &env)
@@ -71,6 +71,7 @@ func (api *API) EnvironmentApiCreate(w http.ResponseWriter, r *http.Request) {
return
}
w.WriteHeader(http.StatusCreated)
api.respondWithSuccess(w, resp)
}
@@ -103,6 +104,7 @@ func (api *API) EnvironmentApiUpdate(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
api.respondWithError(w, err)
return
}
var env fission.Environment
+1 -1
View File
@@ -19,7 +19,7 @@ package controller
import (
"github.com/satori/go.uuid"
"github.com/platform9/fission"
"github.com/fission/fission"
)
type EnvironmentStore struct {
+7 -1
View File
@@ -21,7 +21,7 @@ import (
"os"
"path"
log "github.com/Sirupsen/logrus"
log "github.com/sirupsen/logrus"
)
type requestType int
@@ -62,6 +62,9 @@ func MakeFileStore(path string) *FileStore {
return nil
}
log.Printf("Created directory %v", path)
} else {
log.Printf("Unknown error initializing filestore: %v", err)
return nil
}
}
@@ -86,6 +89,9 @@ func (fs *FileStore) fileStoreService() {
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")
}
+39 -4
View File
@@ -17,15 +17,17 @@ limitations under the License.
package controller
import (
"encoding/base64"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httputil"
"net/url"
"encoding/json"
log "github.com/Sirupsen/logrus"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"encoding/base64"
"github.com/platform9/fission"
"github.com/fission/fission"
)
func (api *API) FunctionApiList(w http.ResponseWriter, r *http.Request) {
@@ -48,6 +50,7 @@ func (api *API) FunctionApiCreate(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
api.respondWithError(w, err)
return
}
var f fission.Function
@@ -77,6 +80,7 @@ func (api *API) FunctionApiCreate(w http.ResponseWriter, r *http.Request) {
return
}
w.WriteHeader(http.StatusCreated)
api.respondWithSuccess(w, resp)
}
@@ -115,6 +119,7 @@ func (api *API) FunctionApiUpdate(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
api.respondWithError(w, err)
return
}
var f fission.Function
@@ -170,3 +175,33 @@ func (api *API) FunctionApiDelete(w http.ResponseWriter, r *http.Request) {
api.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) {
vars := mux.Vars(r)
// get dbType from url
dbType := vars["dbType"]
// find correspond db http url
dbCnf := api.getLogDBConfig(dbType)
svcUrl, err := url.Parse(dbCnf.httpURL)
if err != nil {
log.Printf("Failed to establish proxy server for function logs: %v", err)
}
// set up proxy server director
director := func(req *http.Request) {
// only replace url Scheme and Host to remote influxDB
// and leave query string intact
req.URL.Scheme = svcUrl.Scheme
req.URL.Host = svcUrl.Host
req.URL.Path = svcUrl.Path
// set up http basic auth for database authentication
req.SetBasicAuth(dbCnf.username, dbCnf.password)
}
proxy := &httputil.ReverseProxy{
Director: director,
}
proxy.ServeHTTP(w, r)
}
+22 -3
View File
@@ -17,9 +17,9 @@ limitations under the License.
package controller
import (
log "github.com/Sirupsen/logrus"
log "github.com/sirupsen/logrus"
"github.com/platform9/fission"
"github.com/fission/fission"
)
type FunctionStore struct {
@@ -108,7 +108,26 @@ func (fs *FunctionStore) Delete(m fission.Metadata) error {
if err != nil {
return err
}
return fs.ResourceStore.delete(typeName, m.Name)
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) {
+19 -2
View File
@@ -21,10 +21,10 @@ import (
"io/ioutil"
"net/http"
log "github.com/Sirupsen/logrus"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"github.com/platform9/fission"
"github.com/fission/fission"
)
func (api *API) HTTPTriggerApiList(w http.ResponseWriter, r *http.Request) {
@@ -47,6 +47,7 @@ func (api *API) HTTPTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
api.respondWithError(w, err)
return
}
var t fission.HTTPTrigger
@@ -56,6 +57,20 @@ func (api *API) HTTPTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
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
}
}
uid, err := api.HTTPTriggerStore.Create(&t)
if err != nil {
api.respondWithError(w, err)
@@ -69,6 +84,7 @@ func (api *API) HTTPTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
return
}
w.WriteHeader(http.StatusCreated)
api.respondWithSuccess(w, resp)
}
@@ -101,6 +117,7 @@ func (api *API) HTTPTriggerApiUpdate(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
api.respondWithError(w, err)
return
}
var t fission.HTTPTrigger
+1 -1
View File
@@ -19,7 +19,7 @@ package controller
import (
"github.com/satori/go.uuid"
"github.com/platform9/fission"
"github.com/fission/fission"
)
type HTTPTriggerStore struct {
+166
View File
@@ -0,0 +1,166 @@
/*
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"
log "github.com/sirupsen/logrus"
"github.com/fission/fission"
)
func (api *API) MessageQueueTriggerApiList(w http.ResponseWriter, r *http.Request) {
mqType := r.FormValue("mqtype")
triggers, err := api.MessageQueueTriggerStore.List(mqType)
if err != nil {
api.respondWithError(w, err)
return
}
resp, err := json.Marshal(triggers)
if err != nil {
api.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
}
func (api *API) MessageQueueApiCreate(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
api.respondWithError(w, err)
return
}
var mqTrigger fission.MessageQueueTrigger
err = json.Unmarshal(body, &mqTrigger)
if err != nil {
api.respondWithError(w, err)
return
}
// trigger name must not conflict with any other trigger
// even they are different message queue type
triggers, err := api.MessageQueueTriggerStore.List("")
if err != nil {
api.respondWithError(w, err)
return
}
for _, trigger := range triggers {
if trigger.Name == mqTrigger.Name {
err = fission.MakeError(fission.ErrorNameExists,
"Message queue trigger with same name already exists")
api.respondWithError(w, err)
return
}
}
// save trigger info
uid, err := api.MessageQueueTriggerStore.Create(&mqTrigger)
if err != nil {
api.respondWithError(w, err)
return
}
mqTriggerMeta := fission.Metadata{Name: mqTrigger.Metadata.Name, Uid: uid}
resp, err := json.Marshal(mqTriggerMeta)
if err != nil {
api.respondWithError(w, err)
return
}
w.WriteHeader(http.StatusCreated)
api.respondWithSuccess(w, resp)
}
func (api *API) MessageQueueApiGet(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
mqTriggerMeta := fission.Metadata{
Name: vars["mqTrigger"],
Uid: r.FormValue("uid"), // empty if uid is absent
}
mqTrigger, err := api.MessageQueueTriggerStore.Get(&mqTriggerMeta)
if err != nil {
api.respondWithError(w, err)
return
}
resp, err := json.Marshal(mqTrigger)
if err != nil {
api.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
}
func (api *API) MessageQueueApiUpdate(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
mqtName := vars["mqTrigger"]
body, err := ioutil.ReadAll(r.Body)
if err != nil {
api.respondWithError(w, err)
return
}
var mqTrigger fission.MessageQueueTrigger
err = json.Unmarshal(body, &mqTrigger)
if err != nil {
api.respondWithError(w, err)
return
}
if mqtName != mqTrigger.Metadata.Name {
err = fission.MakeError(fission.ErrorInvalidArgument, "Message queue trigger name doesn't match URL")
api.respondWithError(w, err)
return
}
uid, err := api.MessageQueueTriggerStore.Update(&mqTrigger)
if err != nil {
api.respondWithError(w, err)
return
}
mqTriggerMeta := fission.Metadata{Name: mqTrigger.Metadata.Name, Uid: uid}
resp, err := json.Marshal(mqTriggerMeta)
if err != nil {
api.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
}
func (api *API) MessageQueueApiDelete(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
mqTriggerMeta := fission.Metadata{
Name: vars["mqTrigger"],
Uid: r.FormValue("uid"), // empty if uid is absent
}
if len(mqTriggerMeta.Uid) == 0 {
log.WithFields(log.Fields{"mqTrigger": mqTriggerMeta.Name}).Info("Deleting all versions")
}
err := api.MessageQueueTriggerStore.Delete(mqTriggerMeta)
if err != nil {
api.respondWithError(w, err)
return
}
api.respondWithSuccess(w, []byte(""))
}
+80
View File
@@ -0,0 +1,80 @@
/*
CopyrigmqTrigger 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
mqTriggertp://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/fission/fission"
uuid "github.com/satori/go.uuid"
)
type MessageQueueTriggerStore struct {
ResourceStore
}
func (mqs *MessageQueueTriggerStore) Create(mqTrigger *fission.MessageQueueTrigger) (string, error) {
mqTrigger.Metadata.Uid = uuid.NewV4().String()
return mqTrigger.Metadata.Uid, mqs.ResourceStore.create(mqTrigger)
}
func (mqs *MessageQueueTriggerStore) Get(m *fission.Metadata) (*fission.MessageQueueTrigger, error) {
var mqTrigger fission.MessageQueueTrigger
err := mqs.ResourceStore.read(m.Name, &mqTrigger)
if err != nil {
return nil, err
}
return &mqTrigger, nil
}
func (mqs *MessageQueueTriggerStore) Update(mqTrigger *fission.MessageQueueTrigger) (string, error) {
mqTrigger.Metadata.Uid = uuid.NewV4().String()
return mqTrigger.Metadata.Uid, mqs.ResourceStore.update(mqTrigger)
}
func (mqs *MessageQueueTriggerStore) Delete(m fission.Metadata) error {
typeName, err := getTypeName(fission.MessageQueueTrigger{})
if err != nil {
return err
}
return mqs.ResourceStore.delete(typeName, m.Name)
}
func (mqs *MessageQueueTriggerStore) List(mqType string) ([]fission.MessageQueueTrigger, error) {
typeName, err := getTypeName(fission.MessageQueueTrigger{})
if err != nil {
return nil, err
}
bufs, err := mqs.ResourceStore.getAll(typeName)
if err != nil {
return nil, err
}
triggers := make([]fission.MessageQueueTrigger, 0, len(bufs))
js := JsonSerializer{}
for _, buf := range bufs {
var mqTrigger fission.MessageQueueTrigger
err = js.deserialize([]byte(buf), &mqTrigger)
if err != nil {
return nil, err
}
if len(mqType) > 0 && mqType != mqTrigger.MessageQueueType {
continue
}
triggers = append(triggers, mqTrigger)
}
return triggers, nil
}
+48 -15
View File
@@ -18,13 +18,17 @@ package controller
import (
"errors"
"fmt"
"reflect"
"strings"
"time"
log "github.com/Sirupsen/logrus"
"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 (
@@ -93,7 +97,7 @@ func (rs *ResourceStore) create(r resource) error {
_, err = rs.KeysAPI.Set(context.Background(), key, string(serialized),
&client.SetOptions{PrevExist: client.PrevNoExist})
return err
return handleEtcdErrorForResource(err, r)
}
func (rs *ResourceStore) read(rkey string, res resource) error {
@@ -105,7 +109,7 @@ func (rs *ResourceStore) read(rkey string, res resource) error {
resp, err := rs.KeysAPI.Get(context.Background(), key, nil)
if err != nil {
return err
return handleEtcdError(err, typName, rkey)
}
return rs.serializer.deserialize([]byte(resp.Node.Value), res)
}
@@ -123,24 +127,24 @@ func (rs *ResourceStore) update(r resource) error {
_, err = rs.KeysAPI.Set(context.Background(), key, string(serialized),
&client.SetOptions{PrevExist: client.PrevExist})
return err
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 err
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})
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, err
return nil, handleEtcdError(err, "", key)
}
res := make([]string, 0, len(resp.Node.Nodes))
@@ -162,7 +166,7 @@ func (rs *ResourceStore) writeFile(parentKey string, contents []byte) (string, s
resp, err := rs.KeysAPI.CreateInOrder(context.Background(), parentKey, uid, nil)
if err != nil {
_ = rs.FileStore.delete(uid)
return "", "", err
return "", "", handleEtcdError(err, "file", parentKey)
}
return resp.Node.Key, uid, nil
@@ -172,7 +176,7 @@ 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, err
return nil, handleEtcdError(err, "file", key)
}
if uid == nil {
@@ -201,7 +205,7 @@ 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 err
return handleEtcdError(err, "file", key)
}
var node *client.Node
@@ -222,12 +226,12 @@ func (rs *ResourceStore) deleteFile(key string, uid string) error {
_, err = rs.KeysAPI.Delete(context.Background(), node.Key, nil)
if err != nil {
return err
return handleEtcdError(err, "", node.Key)
}
if len(resp.Node.Nodes) == 1 {
_, err = rs.KeysAPI.Delete(context.Background(), key, &client.DeleteOptions{Dir: true})
return err
return handleEtcdError(err, "file", key)
}
return nil
}
@@ -236,7 +240,7 @@ 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 err
return handleEtcdError(err, "file", key)
}
for _, u := range resp.Node.Nodes {
err = rs.FileStore.delete(u.Value)
@@ -246,9 +250,38 @@ func (rs *ResourceStore) deleteAllFiles(key string) error {
_, err = rs.KeysAPI.Delete(context.Background(), u.Key, nil)
if err != nil {
return err
return handleEtcdError(err, "", u.Key)
}
}
_, err = rs.KeysAPI.Delete(context.Background(), key, &client.DeleteOptions{Dir: true})
return err
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)
}
+183
View File
@@ -0,0 +1,183 @@
/*
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"
"github.com/robfig/cron"
log "github.com/sirupsen/logrus"
"github.com/fission/fission"
)
func (api *API) TimeTriggerApiList(w http.ResponseWriter, r *http.Request) {
triggers, err := api.TimeTriggerStore.List()
if err != nil {
api.respondWithError(w, err)
return
}
resp, err := json.Marshal(triggers)
if err != nil {
api.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
}
func (api *API) TimeTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
api.respondWithError(w, err)
return
}
var t fission.TimeTrigger
err = json.Unmarshal(body, &t)
if err != nil {
api.respondWithError(w, err)
return
}
triggers, err := api.TimeTriggerStore.List()
if err != nil {
api.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)
if err != nil {
err = fission.MakeError(fission.ErrorInvalidArgument, "TimeTrigger cron spec is not valid")
api.respondWithError(w, err)
return
}
uid, err := api.TimeTriggerStore.Create(&t)
if err != nil {
api.respondWithError(w, err)
return
}
m := &fission.Metadata{Name: t.Metadata.Name, Uid: uid}
resp, err := json.Marshal(m)
if err != nil {
api.respondWithError(w, err)
return
}
w.WriteHeader(http.StatusCreated)
api.respondWithSuccess(w, resp)
}
func (api *API) TimeTriggerApiGet(w http.ResponseWriter, r *http.Request) {
var m fission.Metadata
vars := mux.Vars(r)
m.Name = vars["timeTrigger"]
m.Uid = r.FormValue("uid") // empty if uid is absent
t, err := api.TimeTriggerStore.Get(&m)
if err != nil {
api.respondWithError(w, err)
return
}
resp, err := json.Marshal(t)
if err != nil {
api.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
}
func (api *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)
return
}
var t fission.TimeTrigger
err = json.Unmarshal(body, &t)
if err != nil {
api.respondWithError(w, err)
return
}
if name != t.Metadata.Name {
err = fission.MakeError(fission.ErrorInvalidArgument, "TimeTrigger name doesn't match URL")
api.respondWithError(w, err)
return
}
_, err = cron.Parse(t.Cron)
if err != nil {
err = fission.MakeError(fission.ErrorInvalidArgument, "TimeTrigger cron spec is not valid")
api.respondWithError(w, err)
return
}
uid, err := api.TimeTriggerStore.Update(&t)
if err != nil {
api.respondWithError(w, err)
return
}
m := &fission.Metadata{Name: t.Metadata.Name, Uid: uid}
resp, err := json.Marshal(m)
if err != nil {
api.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
}
func (api *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")
}
err := api.TimeTriggerStore.Delete(m)
if err != nil {
api.respondWithError(w, err)
return
}
api.respondWithSuccess(w, []byte(""))
}
+79
View File
@@ -0,0 +1,79 @@
/*
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
}
+123
View File
@@ -0,0 +1,123 @@
/*
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"
log "github.com/sirupsen/logrus"
"github.com/fission/fission"
)
func (api *API) WatchApiList(w http.ResponseWriter, r *http.Request) {
watches, err := api.WatchStore.List()
if err != nil {
api.respondWithError(w, err)
return
}
resp, err := json.Marshal(watches)
if err != nil {
api.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
}
func (api *API) WatchApiCreate(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
api.respondWithError(w, err)
return
}
var watch fission.Watch
err = json.Unmarshal(body, &watch)
if err != nil {
api.respondWithError(w, err)
return
}
watch.Target = fission.UrlForFunction(&watch.Function)
uid, err := api.WatchStore.Create(&watch)
if err != nil {
api.respondWithError(w, err)
return
}
m := &fission.Metadata{Name: watch.Metadata.Name, Uid: uid}
resp, err := json.Marshal(m)
if err != nil {
api.respondWithError(w, err)
return
}
w.WriteHeader(http.StatusCreated)
api.respondWithSuccess(w, resp)
}
func (api *API) WatchApiGet(w http.ResponseWriter, r *http.Request) {
var m fission.Metadata
vars := mux.Vars(r)
m.Name = vars["watch"]
m.Uid = r.FormValue("uid") // empty if uid is absent
watch, err := api.WatchStore.Get(&m)
if err != nil {
api.respondWithError(w, err)
return
}
resp, err := json.Marshal(watch)
if err != nil {
api.respondWithError(w, err)
return
}
api.respondWithSuccess(w, resp)
}
func (api *API) WatchApiUpdate(w http.ResponseWriter, r *http.Request) {
api.respondWithError(w, fission.MakeError(fission.ErrorNotImplmented,
"Not implemented"))
}
func (api *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")
}
err := api.WatchStore.Delete(m)
if err != nil {
api.respondWithError(w, err)
return
}
api.respondWithSuccess(w, []byte(""))
}
+79
View File
@@ -0,0 +1,79 @@
/*
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
}
+6
View File
@@ -0,0 +1,6 @@
bin/*
obj/*
out/*
.vscode/*
project.lock*
TODO_dotnet
+9
View File
@@ -0,0 +1,9 @@
FROM microsoft/dotnet:1.1.0-runtime
WORKDIR /fission-workdir
COPY out .
EXPOSE 8888
ENTRYPOINT ["dotnet"]
CMD ["fission-dotnet.dll"]
+82
View File
@@ -0,0 +1,82 @@
using Fission.DotNetCore.Compiler;
using Fission.DotNetCore.Api;
using System.Collections.Generic;
using Nancy;
using System.IO;
using System;
namespace Fission.DotNetCore
{
public class ExecutorModule : NancyModule
{
#if DEBUG
private const string CODE_PATH = "/tmp/func.cs";
#else
private const string CODE_PATH = "/userfunc/user";
#endif
private static Function _userFunc;
private static Logger _logger = new Logger();
public ExecutorModule()
{
Post("/specialize", args => Specialize());
Get("/", _ => Run());
Post("/", _ => Run());
Put("/", _ => Run());
Head("/", _ => Run());
Options("/", _ => Run());
Delete("/", _ => Run());
}
private object Specialize()
{
var errors = new List<string>();
if (File.Exists(CODE_PATH))
{
var code = File.ReadAllText(CODE_PATH);
_userFunc = FissionCompiler.Compile(code, out errors);
if (_userFunc == null)
{
var errstr = string.Join(Environment.NewLine, errors);
_logger.WriteError(errstr);
var response = (Response)errstr;
response.StatusCode = HttpStatusCode.InternalServerError;
return response;
}
return null;
}
else
{
var errstr = $"Unable to locate code at '{CODE_PATH}'";
_logger.WriteError(errstr);
var response = (Response)errstr;
response.StatusCode = HttpStatusCode.InternalServerError;
return response;
}
}
private object Run()
{
if (_userFunc == null)
{
var response = (Response)"Generic container: no requests supported";
response.StatusCode = HttpStatusCode.InternalServerError;
return response;
}
try
{
return _userFunc.Invoke(FissionContext.Build(Request, new Logger()));
}
catch (Exception e)
{
_logger.WriteError(e.ToString());
var response = (Response)e.Message;
response.StatusCode = HttpStatusCode.BadRequest;
return response;
}
}
}
}
+75
View File
@@ -0,0 +1,75 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
namespace Fission.DotNetCore.Compiler
{
//adapted from this article http://www.tugberkugurlu.com/archive/compiling-c-sharp-code-into-memory-and-executing-it-with-roslyn
class FissionCompiler
{
public static Function Compile(string code, out List<string> errors)
{
errors = new List<string>();
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
string assemblyName = Path.GetRandomFileName();
var coreDir = Directory.GetParent(typeof(Enumerable).GetTypeInfo().Assembly.Location);
List<MetadataReference> references = new List<MetadataReference>
{
MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar + "mscorlib.dll"),
MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(Assembly.GetEntryAssembly().Location),
MetadataReference.CreateFromFile(typeof(System.Runtime.Serialization.Json.DataContractJsonSerializer).GetTypeInfo().Assembly.Location)
};
foreach (var referencedAssembly in Assembly.GetEntryAssembly().GetReferencedAssemblies())
{
var assembly = Assembly.Load(referencedAssembly);
references.Add(MetadataReference.CreateFromFile(assembly.Location));
}
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(
OutputKind.DynamicallyLinkedLibrary,
optimizationLevel: OptimizationLevel.Release));
using (var ms = new MemoryStream())
{
EmitResult result = compilation.Emit(ms);
if (!result.Success)
{
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error).ToList();
foreach (Diagnostic diagnostic in failures)
{
errors.Add($"{diagnostic.Id}: {diagnostic.GetMessage()}");
}
}
else
{
ms.Seek(0, SeekOrigin.Begin);
Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(ms);
var type = assembly.GetType("FissionFunction");
var info = type.GetMember("Execute").First() as MethodInfo;
return new Function(assembly, type, info);
}
}
return null;
}
}
}
+114
View File
@@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Nancy;
namespace Fission.DotNetCore.Api
{
public class FissionContext
{
public FissionContext(Dictionary<string, object> args, Logger logger, FissionHttpRequest request)
{
if (args == null) throw new ArgumentNullException(nameof(args));
if (logger == null) throw new ArgumentNullException(nameof(logger));
if (request == null) throw new ArgumentNullException(nameof(request));
Arguments = args;
Logger = logger;
Request = request;
}
public Dictionary<string, object> Arguments { get; private set; }
public FissionHttpRequest Request { get; private set; }
public Logger Logger { get; private set; }
public static FissionContext Build(Request request, Logger logger)
{
return new FissionContext(((DynamicDictionary)request.Query).ToDictionary(),
logger,
new FissionHttpRequest(request));
}
}
public class Logger
{
public void Write(Severity severity, string format, params object[] args)
{
Console.WriteLine($"{DateTime.Now.ToString("MM/dd/yy H:mm:ss zzz")} {severity}: " + format, args);
}
public void WriteInfo(string format, params object[] args)
{
Write(Severity.Info, format, args);
}
public void WriteWarning(string format, params object[] args)
{
Write(Severity.Warning, format, args);
}
public void WriteError(string format, params object[] args)
{
Write(Severity.Error, format, args);
}
public void WriteCritical(string format, params object[] args)
{
Write(Severity.Critical, format, args);
}
public void WriteVerbose(string format, params object[] args)
{
Write(Severity.Verbose, format, args);
}
}
public enum Severity
{
Info,
Warning,
Error,
Critical,
Verbose
}
public class FissionHttpRequest
{
private readonly Request _request;
internal FissionHttpRequest(Request request)
{
if (request == null) throw new ArgumentNullException(nameof(request));
_request = request;
}
public Stream Body { get { return _request.Body; } }
public string BodyAsString()
{
int length = (int)_request.Body.Length;
byte[] data = new byte[length];
_request.Body.Read(data, 0, length);
return Encoding.UTF8.GetString(data);
}
public Dictionary<string, IEnumerable<string>> Headers
{
get
{
var headers = new Dictionary<string, IEnumerable<string>>();
foreach (var kv in _request.Headers)
{
headers.Add(kv.Key, kv.Value);
}
return headers;
}
}
public X509Certificate Certificate { get { return _request.ClientCertificate; } }
public string Url { get { return _request.Url.ToString(); } }
public string Method { get { return _request.Method; } }
}
}
+28
View File
@@ -0,0 +1,28 @@
using System;
using System.Reflection;
using Fission.DotNetCore.Api;
namespace Fission.DotNetCore.Compiler
{
class Function
{
private readonly Assembly _assembly;
private readonly Type _type;
private readonly MethodInfo _info;
public Function(Assembly assembly, Type type, MethodInfo info)
{
if (info == null) throw new ArgumentNullException(nameof(info));
if (assembly == null) throw new ArgumentNullException(nameof(assembly));
if (type == null) throw new ArgumentNullException(nameof(type));
_assembly = assembly;
_type = type;
_info = info;
}
public object Invoke(FissionContext context)
{
return _info.Invoke(_assembly.CreateInstance(_type.FullName), new[] { context });
}
}
}
+21
View File
@@ -0,0 +1,21 @@
using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Nancy.Owin;
namespace Fission.DotNetCore
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseKestrel()
.UseUrls("http://*:8888")
.Configure(app => app.UseOwin(x => x.UseNancy()))
.Build();
host.Run();
}
}
}
+306
View File
@@ -0,0 +1,306 @@
# Fission: dotnet C# Environment
This is a simple dotnet C# environment for Fission.
It's a Docker image containing the dotnet 1.1.0 runtime. The image
uses Kestrel with Nancy to host the internal web server and uses
Roslyn to compile the uploaded code.
The image supports compiling and running code with types defined in
mscorlib and does not at present support other library references.
One workaround for this would be to add the references to this project's
project.json file and rebuild the container.
The environment works via convention where you create a C# class
called FissionFunction which has a method named Execute taking a single
parameter, a FissionContext object.
The FissionContext object gives access to the arguments and other items
like logging. Please see FissionContext.cs for public API.
Example of simplest possible class to be executed:
```
using System;
using Fission.DotNetCore.Api;
public class FissionFunction {
public string Execute(FissionContext context) {
return null;
}
}
```
Please see examples below.
## Rebuilding and pushing the image
To rebuild the image you need either a computer with dotnet 1.1.0
installed or else you will have to map the source directory into a
container containing the dotnet 1.1.0 environment.
### Locally installed Dotnet 1.1.0
Simply move to the source directory in a terminal and run the ./build.sh script.
The script will restore dependencies, compile a release build and
and build the container. If you need to change the name of the container
simply change it in the script.
After the build finishes push the new image to a Docker registry using the
standard procedure.
### Build in a container
Move to the directory containing the source and start the Docker container
with dotnet and mount the current directory to a build location:
```
docker run -it --rm -v $PWD:/build microsoft/dotnet
```
Move to the build directory inside the container and restore the packages:
```
cd /build
dotnet restore
log : Restoring packages for /source/project.json...
log : Installing System.Net.WebSockets 4.0.0.
log : Installing runtime.native.System.IO.Compression 4.1.0.
...
```
Compile and publish a release build of the source to the 'out' folder:
```
dotnet publish -c Release -o out
Publishing source for .NETCoreApp,Version
...
```
Exit the build container and build the Docker container on the local host:
```
exit
docker build -t USER/dotnet-env .
```
After the build finishes push the new image to a Docker registry using the
standard procedure.
## Echo example
### Setup fission environment
First you need to setup the fission according to your cluster setup as
specified here: https://github.com/fission/fission
### Create the class to run
Secondly you need to create a file /tmp/func.cs containing the following code:
```
using System;
using Fission.DotNetCore.Api;
public class FissionFunction
{
public string Execute(FissionContext context){
context.Logger.WriteInfo("executing.. {0}", context.Arguments["text"]);
return (string)context.Arguments["text"];
}
}
```
### Run the example
Lastly to run the example:
```
$ fission env create --name dotnet --image fission/dotnet-env
$ fission function create --name echo --env dotnet --code /tmp/func.cs
$ fission route create --method GET --url /echo --function echo
$ curl http://$FISSION_ROUTER/echo?text=hello%20world!
hello world
```
## Addition service example
### Setup fission environment
First you need to setup the fission according to your cluster setup as
specified here: https://github.com/fission/fission
### Create the class to run
Secondly you need to create a file /tmp/func.cs containing the following code:
```
using System;
using Fission.DotNetCore.Api;
public class FissionFunction
{
public string Execute(FissionContext context){
var x = Convert.ToInt32(context.Arguments["x"]);
var y = Convert.ToInt32(context.Arguments["y"]);
return (x+y).ToString();
}
}
```
### Run the example
Lastly to run the example:
```
$ fission env create --name dotnet --image fission/dotnet-env
$ fission function create --name addition --env dotnet --code /tmp/func.cs
$ fission route create --method GET --url /add --function addition
$ curl "http://$FISSION_ROUTER/add?x=30&y=12"
42
```
## Accessing http request information example
### Setup fission environment
First you need to setup the fission according to your cluster setup as
specified here: https://github.com/fission/fission
### Create the class to run
Secondly you need to create a file /tmp/func.cs containing the following code:
```
using System;
using Fission.DotNetCore.Api;
public class FissionFunction
{
public string Execute(FissionContext context){
var buffer = new System.Text.StringBuilder();
foreach(var header in context.Request.Headers){
buffer.AppendLine(header.Key);
foreach(var item in header.Value){
buffer.AppendLine($"\t{item}");
}
}
buffer.AppendLine($"Url: {context.Request.Url}, method: {context.Request.Method}");
return buffer.ToString();
}
}
```
### Run the example
Lastly to run the example:
```
$ fission env create --name dotnet --image fission/dotnet-env
$ fission function create --name httpinfo --env dotnet --code /tmp/func.cs
$ fission route create --method GET --url /http_info --function httpinfo
$ curl "http://$FISSION_ROUTER/http_info"
Accept
*/*;q=1
Host
fissionserver:8888
User-Agent
curl/7.47.0
Url: http://fissionserver:8888, method: GET
```
## Accessing http request body example
### Setup fission environment
First you need to setup the fission according to your cluster setup as
specified here: https://github.com/fission/fission
### Create the class to run
Secondly you need to create a file /tmp/func.cs containing the following code:
```
using System.IO;
using System.Runtime.Serialization.Json;
using Fission.DotNetCore.Api;
public class FissionFunction
{
public string Execute(FissionContext context)
{
var person = Person.Deserialize(context.Request.Body);
return $"Hello, my name is {person.Name} and I am {person.Age} years old.";
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public static Person Deserialize(Stream json)
{
var serializer = new DataContractJsonSerializer(typeof(Person));
return (Person)serializer.ReadObject(json);
}
}
```
### Run the example
Lastly to run the example:
```
$ fission env create --name dotnet --image fission/dotnet-env
$ fission function create --name httpbody --env dotnet --code /tmp/func.cs
$ fission route create --method GET --url /http_body --function httpbody
$ curl -XPOST "http://$FISSION_ROUTER/http_body" -d '{ "Name":"Arthur", "Age":42}'
Hello, my name is Arthur and I am 42 years old.
```
## Developing/debugging the enviroment locally
The easiest way to debug the environment is to open the directory in
Visual Studio Code (VSCode) as that will setup debugger for you the
first time.
Remember to install the excellent extension
"C# for Visual Studio Code(powered by OmniSharp)" to get statement completion
The class ExecutorModule contain preprocessor directive overriding where
the input code file should be found:
```
#if DEBUG
private const string CODE_PATH = "/tmp/func.cs";
#else
private const string CODE_PATH = "/userfunc/user";
#endif
```
So what you need to do is:
1. Open the directory in VSCode.
This will prompt restore of packages and query is debugger setup is needed. Accept both prompts.
2. Press F5 to start the web server. Set breakpoints etc..
3. Add a code file containing valid C# at /tmp/func.cs
4. Specialize the service with curl via post
```
$ curl -XPOST http://localhost:8888/specialize
```
5. Call your function with curl
```
$ curl -XGET http://localhost:8888
```
+3
View File
@@ -0,0 +1,3 @@
dotnet restore
dotnet publish -c Release -o out
docker build -t fission/dotnet-env .
+24
View File
@@ -0,0 +1,24 @@
{
"version": "1.0.0-*",
"buildOptions": {
"emitEntryPoint": true,
"outputName": "fission-dotnet"
},
"frameworks": {
"netcoreapp1.0": {
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.1.0",
"type": "platform"
},
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
"Microsoft.AspNetCore.Owin": "1.0.0",
"Nancy": "2.0.0-barneyrubble",
"Microsoft.CodeAnalysis.CSharp": "1.3.0-*",
"System.Runtime.Loader": "4.0.0-*",
"System.Runtime.Serialization.Json": "4.0.2"
},
"imports": "netstandard1.3"
}
}
}
+14
View File
@@ -0,0 +1,14 @@
FROM golang:1.8.1
ENV GOPATH /usr
ENV APP ${GOPATH}/src/github.com/fission/fission/environments/go
ADD context ${APP}/context
ADD server.go ${APP}
WORKDIR ${APP}
RUN go get
RUN go build -o /server server.go
ENTRYPOINT ["/server"]
EXPOSE 8888
+40
View File
@@ -0,0 +1,40 @@
# Fission: Go Environment
This is the Go environment for Fission.
It's a Docker image containing a Go 1.8 runtime, along with a dynamic loader.
## Build this image
```
docker build -t USER/go-runtime . && docker push USER/go-runtime
```
Note that if you build the runtime, you must also build the go-builder
image, to ensure that it's at the same version of go:
```
cd builder && docker build -t USER/go-builder . && docker push USER/go-builder
```
## Using the image in fission
You can add this customized image to fission with "fission env
create":
```
fission env create --name go-runtime --image USER/go-runtime
```
Or, if you already have an environment, you can update its image:
```
fission env update --name go-runtime --image USER/go-runtime
```
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/go/README.md).
+5
View File
@@ -0,0 +1,5 @@
FROM golang:1.8.1
ENV GOPATH /usr
RUN go get github.com/fission/fission
+11
View File
@@ -0,0 +1,11 @@
#!/bin/sh
if [ $# -eq 0 ]
then
echo "No arguments, building current dir"
filename='.'
else
filename=$1
fi
docker run --rm -v $(pwd):/build -w /build fission/go-builder:1.8.1 go build -buildmode=plugin -o function.so $filename && echo "Compiled plugin: function.so. Use 'fission function create' to upload to fission."
+10
View File
@@ -0,0 +1,10 @@
package context
type (
Context map[string]interface{}
)
func New() Context {
ctx := make(map[string]interface{})
return ctx
}
+83
View File
@@ -0,0 +1,83 @@
package main
import (
"fmt"
"net/http"
"os"
"plugin"
"github.com/fission/fission/environments/go/context"
)
const (
CODE_PATH = "/userfunc/user"
)
var userFunc http.HandlerFunc
func loadPlugin() http.HandlerFunc {
p, err := plugin.Open(CODE_PATH)
if err != nil {
panic(err)
}
sym, err := p.Lookup("Handler")
if err != nil {
panic("Entry point not found")
}
switch h := sym.(type) {
case *http.Handler:
return (*h).ServeHTTP
case *http.HandlerFunc:
return *h
case func(http.ResponseWriter, *http.Request):
return h
case func(context.Context, http.ResponseWriter, *http.Request):
return func(w http.ResponseWriter, r *http.Request) {
c := context.New()
h(c, w, r)
}
default:
panic("Entry point not found: bad type")
}
}
func specializeHandler(w http.ResponseWriter, r *http.Request) {
if userFunc != nil {
w.WriteHeader(400)
w.Write([]byte("Not a generic container"))
return
}
_, err := os.Stat(CODE_PATH)
if err != nil {
if os.IsNotExist(err) {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte(CODE_PATH + ": not found"))
return
} else {
panic(err)
}
}
fmt.Println("Specializing ...")
userFunc = loadPlugin()
fmt.Println("Done")
}
func main() {
http.HandleFunc("/specialize", specializeHandler)
// Generic route -- all http requests go to the user function.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if userFunc == nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Generic container: no requests supported"))
return
}
userFunc(w, r)
})
fmt.Println("Listening on 8888 ...")
http.ListenAndServe(":8888", nil)
}
+1
View File
@@ -0,0 +1 @@
node_modules/
+1 -1
View File
@@ -1,6 +1,6 @@
# A docker image for the func container.
FROM node:4-onbuild
FROM node:7-onbuild
ADD server.js /usr/src/app/server.js
+45
View File
@@ -0,0 +1,45 @@
# Fission: NodeJS Environment
This is the NodeJS environment for Fission.
It's a Docker image containing a NodeJS runtime, along with a dynamic
loader. A few common dependencies are included in the package.json
file.
## Customizing this image
To add package dependencies, edit package.json to add what you need,
and rebuild this image (instructions below).
You also may want to customize what's available to the function in its
request context. You can do this by editing `server.js` (see the
comment in that file about customizing request context).
## Rebuilding and pushing the image
You'll need access to a Docker registry to push the image: you can
sign up for Docker hub at hub.docker.com, or use registries from
gcr.io, quay.io, etc. Let's assume you're using a docker hub account
called USER. Build and push the image to the the registry:
```
docker build -t USER/nodejs-env . && docker push USER/nodejs-env
```
## Using the image in fission
You can add this customized image to fission with "fission env
create":
```
fission env create --name nodejs --image USER/nodejs-env
```
Or, if you already have an environment, you can update its image:
```
fission env update --name nodejs --image USER/nodejs-env
```
After this, fission functions that have the env parameter set to the
same environment name as this command will use this environment.
+23 -24
View File
@@ -1,27 +1,26 @@
{
"name": "fission-nodejs-runtime",
"version": "0.0.0",
"author": "Soam Vasani",
"contributors": [
{
"name": "Soam Vasani",
"email": "soamvasani@platform9.com"
}
],
"description": "NodeJS run container for the fission framework",
"engines": {
"node": ">=4.2.2"
},
"dependencies": {
"express": "",
"minimist": "",
"body-parser": "",
"morgan": "",
"co": "~4.6.0",
"request": "",
"request-promise": "^1.0.2",
"mz": "~2.1.0",
"underscore": ">=1.8.3"
"name": "fission-nodejs-runtime",
"version": "0.1.0",
"author": "Soam Vasani",
"contributors": [
{
"name": "Soam Vasani",
"email": "soamvasani@platform9.com"
}
],
"description": "NodeJS run container for the fission framework",
"engines": {
"node": ">=7.6.0"
},
"dependencies": {
"body-parser": "",
"co": "~4.6.0",
"express": "",
"minimist": "",
"morgan": "",
"mz": "~2.1.0",
"request": "^2.81.0",
"request-promise-native": "^1.0.3",
"underscore": ">=1.8.3"
}
}
+38 -4
View File
@@ -1,6 +1,7 @@
'use strict';
const fs = require('fs');
const path = require('path');
const process = require('process');
const express = require('express');
const app = express();
@@ -18,6 +19,16 @@ if (!argv.port) {
argv.port = 8888;
}
// Node resolves module paths according to a file's location. We load
// the file from argv.codepath, but tell users to put dependencies in
// the server's package.json; this means the function's dependencies
// are in /usr/src/app/node_modules. We could be smarter and have the
// function deps in the right place in argv.codepath; but for now we
// just symlink the function's node_modules to the server's
// node_modules.
fs.symlinkSync('/usr/src/app/node_modules', `${path.dirname(argv.codepath)}/node_modules`);
// User function. Starts out undefined.
let userFunction;
@@ -64,11 +75,13 @@ app.all('/', function (req, res) {
res.status(500).send("Generic container: no requests supported");
return;
}
const context = {
request: req,
response: res
// TODO: context should also have: URL template params, query string
};
function callback(status, body, headers) {
if (!status)
return;
@@ -79,11 +92,32 @@ app.all('/', function (req, res) {
}
res.status(status).send(body);
}
try {
userFunction(context, callback);
} catch(e) {
callback(500, "Internal server error")
//
// Customizing the request context
//
// If you want to modify the context to add anything to it,
// you can do that here by adding properties to the context.
//
let functionProm;
if (userFunction.length === 1) { // One argument (context)
// Make sure their function returns a promise
Promise.resolve(userFunction(context)).then(function({ status, body, headers }) {
callback(status, body, headers);
}).catch(function(err) {
console.log(`Function error: ${err}`);
callback(500, "Internal server error");
});
} else { // 2 arguments (context, callback)
try {
userFunction(context, callback);
} catch (err) {
console.log(`Function error: ${err}`);
callback(500, "Internal server error");
}
}
});
app.listen(argv.port);
+5 -2
View File
@@ -1,6 +1,9 @@
module.exports = function (context, callback) {
module.exports = async function (context) {
console.log("headers=", JSON.stringify(context.request.headers));
console.log("body=", JSON.stringify(context.request.body));
callback(200, "Hello, world !\n");
return {
status: 200,
body: "Hello, world !\n"
};
}
+73
View File
@@ -0,0 +1,73 @@
FROM php:7.1-alpine
ENV PATH="/root/.composer/vendor/bin:${PATH}" \
COMPOSER_ALLOW_SUPERUSER=1
RUN echo '#!/bin/sh' > /usr/local/bin/apk-install \
&& echo 'apk add --update "$@" && rm -rf /var/cache/apk/*' >> /usr/local/bin/apk-install \
&& chmod +x /usr/local/bin/apk-install
RUN echo 'http://dl-4.alpinelinux.org/alpine/edge/testing' >> /etc/apk/repositories \
&& apk update \
&& apk-install \
git \
curl \
curl-dev \
libcurl \
zlib-dev \
freetype-dev \
jpeg-dev \
libjpeg-turbo-dev \
postgresql-dev \
libmcrypt-dev \
libpng-dev \
icu-dev \
gettext-dev \
vim \
libxml2-dev \
freetype-dev \
unzip \
libc6-compat \
openssl \
gcc \
autoconf
RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/
# Install useful extensions
RUN docker-php-ext-install \
opcache \
bcmath \
ctype \
curl \
dom \
iconv \
fileinfo \
gd \
gettext \
intl \
json \
mcrypt \
mysqli \
pgsql \
pcntl \
pdo \
ftp \
pdo_mysql \
pdo_pgsql \
phar \
simplexml \
xmlrpc \
zip
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
COPY . /app
WORKDIR /app
RUN composer install
EXPOSE 8888
ENTRYPOINT ["php"]
CMD ["-S","0.0.0.0:8888","index.php"]
+56
View File
@@ -0,0 +1,56 @@
# Fission: PHP Environment
This is the PHP environment for Fission.
It's a Docker image containing a PHP 7.1 runtime. This image use php:7.1-cli base image with the built-in PHP server
This environment didn't force you to use class or create a main function.
A few common extensions are included :
- curl
- gd
- json
- mcrypt
- PDO (pdo_mysql, pdo_pgsql, pdo_sqlite)
- OPcache
- ftp
- iconv
- phar
- mysqli
- pgsql
- SimpleXML
- xmlrpc
- zip
## Customizing this image
To add other extensions or packages(composer.json) you need to edit the Dockerfile and rebuild this image (instructions below).
## Rebuilding and pushing the image
You'll need access to a Docker registry to push the image: you can
sign up for Docker hub at hub.docker.com, or use registries from
gcr.io, quay.io, etc. Let's assume you're using a docker hub account
called USER. Build and push the image to the the registry:
```
docker build -t USER/php7-env . && docker push USER/php7-env
```
## Using the image in fission
You can add this customized image to fission with "fission env
create":
```
fission env create --name php7 --image USER/php7-env
```
Or, if you already have an environment, you can update its image:
```
fission env update --name php7 --image USER/php7-env
```
After this, fission functions that have the env parmeter set to the
same environment name as this command will use this environment.
+11
View File
@@ -0,0 +1,11 @@
{
"autoload": {
"psr-4": {
"PHPEnv\\": "src/"
}
},
"require": {
"zendframework/zend-diactoros": "^1.3",
"monolog/monolog": "^1.22"
}
}
+244
View File
@@ -0,0 +1,244 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "26efb7d1cc58e9ecdc8765fac5eca4e1",
"content-hash": "c0c5cd003c9c3ca4cf3346b232e86258",
"packages": [
{
"name": "monolog/monolog",
"version": "1.22.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "bad29cb8d18ab0315e6c477751418a82c850d558"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/bad29cb8d18ab0315e6c477751418a82c850d558",
"reference": "bad29cb8d18ab0315e6c477751418a82c850d558",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"psr/log": "~1.0"
},
"provide": {
"psr/log-implementation": "1.0.0"
},
"require-dev": {
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"doctrine/couchdb": "~1.0@dev",
"graylog2/gelf-php": "~1.0",
"jakub-onderka/php-parallel-lint": "0.9",
"php-amqplib/php-amqplib": "~2.4",
"php-console/php-console": "^3.1.3",
"phpunit/phpunit": "~4.5",
"phpunit/phpunit-mock-objects": "2.3.0",
"ruflin/elastica": ">=0.90 <3.0",
"sentry/sentry": "^0.13",
"swiftmailer/swiftmailer": "~5.3"
},
"suggest": {
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
"ext-mongo": "Allow sending log messages to a MongoDB server",
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
"mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
"php-console/php-console": "Allow sending log messages to Google Chrome",
"rollbar/rollbar": "Allow sending log messages to Rollbar",
"ruflin/elastica": "Allow sending log messages to an Elastic Search server",
"sentry/sentry": "Allow sending log messages to a Sentry server"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Monolog\\": "src/Monolog"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
"homepage": "http://github.com/Seldaek/monolog",
"keywords": [
"log",
"logging",
"psr-3"
],
"time": "2016-11-26 00:15:39"
},
{
"name": "psr/http-message",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"time": "2016-08-06 14:39:51"
},
{
"name": "psr/log",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "Psr/Log/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
"time": "2016-10-10 12:19:37"
},
{
"name": "zendframework/zend-diactoros",
"version": "1.3.10",
"source": {
"type": "git",
"url": "https://github.com/zendframework/zend-diactoros.git",
"reference": "83e8d98b9915de76c659ce27d683c02a0f99fa90"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/83e8d98b9915de76c659ce27d683c02a0f99fa90",
"reference": "83e8d98b9915de76c659ce27d683c02a0f99fa90",
"shasum": ""
},
"require": {
"php": "^5.4 || ^7.0",
"psr/http-message": "~1.0"
},
"provide": {
"psr/http-message-implementation": "~1.0.0"
},
"require-dev": {
"phpunit/phpunit": "^4.6 || ^5.5",
"zendframework/zend-coding-standard": "~1.0.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3-dev",
"dev-develop": "1.4-dev"
}
},
"autoload": {
"psr-4": {
"Zend\\Diactoros\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"description": "PSR HTTP Message implementations",
"homepage": "https://github.com/zendframework/zend-diactoros",
"keywords": [
"http",
"psr",
"psr-7"
],
"time": "2017-01-23 04:53:24"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}
+4
View File
@@ -0,0 +1,4 @@
<?php
require __DIR__ . '/vendor/autoload.php';
$server = new PHPEnv\Server();
$server->run();
+64
View File
@@ -0,0 +1,64 @@
<?php
namespace PHPEnv;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Zend\Diactoros\ServerRequest;
use Zend\Diactoros\Response;
use Zend\Diactoros\Server as ZendServer;
class Server {
private $server;
public function __construct(){
$codepath = '/userfunc/user';
//if i have a local function i use it
$devcodepath = '/app/userfuncdev.php';
if(file_exists($devcodepath))
$codepath = $devcodepath;
$logger = new Logger("Function");
$logger->pushHandler(new StreamHandler('php://stdout', Logger::DEBUG));
$this->server = ZendServer::createServer(
function (ServerRequest $request, Response $response) use ($logger, $codepath) {
$path = parse_url($request->getUri(), PHP_URL_PATH);
if($path == "/specialize" && $request->getMethod() == "POST"){
//Nothing to do
return new Response\EmptyResponse(201);
}else{
if(!file_exists($codepath)){
$response = $response->withStatus(500);
$response->getBody()->write("Generic container: no requests supported");
return $response;
}
ob_start();
include($codepath);
//If the function as an handler class it will be called with request, response and logger
if(function_exists("handler")){
ob_end_clean();
return handler(array("request"=>$request, "response"=>$response,"logger"=>$logger));
}
//php code didn't have handler function, i will return the content
$bodyRowContent = ob_get_contents();
ob_end_clean();
return $response->getBody()->write($bodyRowContent);
}
},
$_SERVER,
$_GET,
$_POST,
$_COOKIE,
$_FILES
);
}
public function run(){
$this->server->listen();
}
}
?>
+14 -3
View File
@@ -1,3 +1,14 @@
FROM python:3-onbuild
CMD [ "python", "./server.py" ]
EXPOSE 8888
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
COPY . /app
WORKDIR /app
RUN pip3 install -r requirements.txt
ENTRYPOINT ["python3"]
CMD ["server.py"]
+45
View File
@@ -0,0 +1,45 @@
# Fission: Python Environment
This is the Python environment for Fission.
It's a Docker image containing a Python 3.5 runtime, along with a
dynamic loader. A few common dependencies are included in the
requirements.txt file.
## Customizing this image
To add package dependencies, edit requirements.txt to add what you
need, and rebuild this image (instructions below).
You also may want to customize what's available to the function in its
request context. You can do this by editing server.py (see the
comment in that file about customizing request context).
## Rebuilding and pushing the image
You'll need access to a Docker registry to push the image: you can
sign up for Docker hub at hub.docker.com, or use registries from
gcr.io, quay.io, etc. Let's assume you're using a docker hub account
called USER. Build and push the image to the the registry:
```
docker build -t USER/python-env . && docker push USER/python-env
```
## Using the image in fission
You can add this customized image to fission with "fission env
create":
```
fission env create --name python --image USER/python-env
```
Or, if you already have an environment, you can update its image:
```
fission env update --name python --image USER/python-env
```
After this, fission functions that have the env parameter set to the
same environment name as this command will use this environment.
+6
View File
@@ -0,0 +1,6 @@
Flask===0.11.1
httplib2
python-dateutil
requests==2.7.0
redis
hiredis
+45 -32
View File
@@ -1,42 +1,55 @@
#!/usr/bin/env python
import logging
import sys
import imp
from http.server import BaseHTTPRequestHandler, HTTPServer
#
# Load the file.
#
from flask import Flask, request, abort, g
app = Flask(__name__)
codepath = '/userfunc/user'
userfunc = None
class testHTTPServer_RequestHandler(BaseHTTPRequestHandler):
def do_POST(self):
global userfunc
userfunc = (imp.load_source('user', codepath)).main
self.send_response(200)
self.end_headers()
self.wfile.write(bytes("ok\n", "utf8"))
return
@app.route('/specialize', methods=['POST'])
def load():
global userfunc
userfunc = (imp.load_source('user', codepath)).main
return ""
# GET
def do_GET(self):
global userfunc
try:
print("GET request")
message = userfunc(None)
self.send_response(200)
self.end_headers()
self.wfile.write(bytes(message, "utf8"))
return
except:
self.send_response(500)
self.end_headers()
@app.route('/', methods=['GET', 'POST', 'PUT', 'HEAD', 'OPTIONS', 'DELETE'])
def f():
if userfunc == None:
print("Generic container: no requests supported")
abort(500)
#
# Customizing the request context
#
# If you want to pass something to the function, you can add it to 'g':
# g.myKey = myValue
# And the user func can then access that (after doing a "from flask import g").
#
return userfunc()
def run():
print('starting server...')
server_address = ('0.0.0.0', 8888)
httpd = HTTPServer(server_address, testHTTPServer_RequestHandler)
httpd.serve_forever()
#
# Logging setup. TODO: Loglevel hard-coded for now. We could allow
# functions/routes to override this somehow; or we could create
# separate dev vs. prod environments.
#
def setup_logger(loglevel):
global app
root = logging.getLogger()
root.setLevel(loglevel)
ch = logging.StreamHandler(sys.stdout)
ch.setLevel(loglevel)
ch.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
app.logger.addHandler(ch)
run()
#
# TODO: this starts the built-in server, which isn't the most
# efficient. We should use something better.
#
setup_logger(logging.DEBUG)
app.logger.info("Starting server")
app.run(host='0.0.0.0', port='8888')
+16
View File
@@ -0,0 +1,16 @@
FROM ruby:2.4.1
RUN apt-get update -qq && apt-get install -y build-essential
RUN mkdir /app
WORKDIR /app
ADD Gemfile /app/Gemfile
ADD Gemfile.lock /app/Gemfile.lock
RUN bundle install
COPY . /app
EXPOSE 8888
ENTRYPOINT ["ruby"]
CMD ["server.rb"]
+4
View File
@@ -0,0 +1,4 @@
# frozen_string_literal: true
source "https://rubygems.org"
gem "rack"
+13
View File
@@ -0,0 +1,13 @@
GEM
remote: https://rubygems.org/
specs:
rack (2.0.3)
PLATFORMS
ruby
DEPENDENCIES
rack
BUNDLED WITH
1.15.1
+55
View File
@@ -0,0 +1,55 @@
# Fission: Ruby Environment
This is the Ruby environment for Fission.
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, and a
request object. 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
To add package dependencies, edit Gemfile to add what you
need, and rebuild this image (instructions below).
## Rebuilding and pushing the image
You'll need access to a Docker registry to push the image: you can
sign up for Docker hub at hub.docker.com, or use registries from
gcr.io, quay.io, etc. Let's assume you're using a docker hub account
called USER. Build and push the image to the the registry:
```
docker build -t USER/ruby-env . && docker push USER/ruby-env
```
## Using the image in fission
You can add this customized image to fission with "fission env
create":
```
fission env create --name ruby --image USER/ruby-env
```
Or, if you already have an environment, you can update its image:
```
fission env update --name ruby --image USER/ruby-env
```
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).
+17
View File
@@ -0,0 +1,17 @@
# frozen_string_literal: true
require_relative 'request'
module Fission
class Context
attr_reader :env
def initialize(env)
@env = env
end
def request
@request ||= Request.new(env)
end
end
end
+19
View File
@@ -0,0 +1,19 @@
# frozen_string_literal: true
require_relative 'context'
module Fission
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
+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
+14
View File
@@ -0,0 +1,14 @@
# frozen_string_literal: true
module Fission
CODE_PATH = '/userfunc/user'
module Specializer
def self.call(env)
load CODE_PATH
Rack::Response.new([], 201).finish
rescue
Rack::Response.new(['500 Internal Server Error'], 500, {}).finish
end
end
end
+20
View File
@@ -0,0 +1,20 @@
# frozen_string_literal: true
require 'rack'
require_relative 'fission/specializer'
require_relative 'fission/handler'
app = Rack::Builder.new do
use Rack::CommonLogger, $stderr
map "/specialize" do
run Fission::Specializer
end
map "/" do
run Fission::Handler
end
end
Rack::Handler::WEBrick.run app, Host: '0.0.0.0', Port: 8888
+30 -10
View File
@@ -18,11 +18,13 @@ package fission
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
)
func (e Error) Error() string {
return fmt.Sprintf("(Error %v) %v", e.Code, e.Message)
func (err Error) Error() string {
return fmt.Sprintf("%v - %v", err.Description(), err.Message)
}
func MakeError(code int, msg string) Error {
@@ -36,29 +38,39 @@ func MakeErrorFromHTTP(resp *http.Response) error {
var errCode int
switch resp.StatusCode {
case 400:
errCode = ErrorInvalidArgument
case 403:
errCode = ErrorNotAuthorized
case 404:
errCode = ErrorNotFound
case 400:
errCode = ErrorInvalidArgument
case 409:
errCode = ErrorNameExists
default:
errCode = ErrorInternal
}
return MakeError(errCode, resp.Status)
msg := resp.Status
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err == nil && len(body) > 0 {
msg = strings.TrimSpace(string(body))
}
return MakeError(errCode, msg)
}
func (err Error) HTTPStatus() int {
var code int
switch err.Code {
case ErrorNotFound:
code = 404
case ErrorInvalidArgument:
code = 400
case ErrorNoSpace:
code = 500
case ErrorNotAuthorized:
code = 403
case ErrorNotFound:
code = 404
case ErrorNameExists:
code = 409
default:
code = 500
}
@@ -70,11 +82,19 @@ func GetHTTPError(err error) (int, string) {
var code int
fe, ok := err.(Error)
if ok {
msg = fe.Message
code = fe.HTTPStatus()
msg = fe.Message
} else {
code = 500
msg = err.Error()
}
return code, msg
}
func (err Error) Description() string {
idx := int(err.Code)
if idx < 0 || idx > len(errorDescriptions)-1 {
return ""
}
return errorDescriptions[idx]
}
+11
View File
@@ -0,0 +1,11 @@
using System;
using Fission.DotNetCore.Api;
public class FissionFunction
{
public string Execute(FissionContext context){
var x = Convert.ToInt32(context.Arguments["x"]);
var y = Convert.ToInt32(context.Arguments["y"]);
return (x+y).ToString();
}
+9
View File
@@ -0,0 +1,9 @@
using Fission.DotNetCore.Api;
public class FissionFunction
{
public string Execute(FissionContext context)
{
return "Hello World!";
}
}
+24
View File
@@ -0,0 +1,24 @@
using System.IO;
using System.Runtime.Serialization.Json;
using Fission.DotNetCore.Api;
public class FissionFunction
{
public string Execute(FissionContext context)
{
var person = Person.Deserialize(context.Request.Body);
return $"Hello, my name is {person.Name} and I am {person.Age} years old.";
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public static Person Deserialize(Stream json)
{
var serializer = new DataContractJsonSerializer(typeof(Person));
return (Person)serializer.ReadObject(json);
}
}
+17
View File
@@ -0,0 +1,17 @@
using System;
using Fission.DotNetCore.Api;
public class FissionFunction
{
public string Execute(FissionContext context){
var buffer = new System.Text.StringBuilder();
foreach(var header in context.Request.Headers){
buffer.AppendLine(header.Key);
foreach(var item in header.Value){
buffer.AppendLine($"\t{item}");
}
}
buffer.AppendLine($"Url: {context.Request.Url}, method: {context.Request.Method}");
return buffer.ToString();
}
}
+40
View File
@@ -0,0 +1,40 @@
# Go examples
The `go` runtime uses the [`plugin` package](https://golang.org/pkg/plugin/) to dynamically load an HTTP handler.
## Requirements
First, set up your fission deployment with the go environment.
```
fission env create --name go-env --image fission/go-env:1.8.1
```
To ensure that you build functions using the same version as the
runtime, fission provides a docker image and helper script for
building functions.
## Example Usage
### hello.go
`hello.go` is an very basic HTTP handler returning `"Hello, World!"`.
```bash
# Download the build helper script
$ curl https://raw.githubusercontent.com/fission/fission/master/environments/go/builder/go-function-build > go-function-build
$ chmod +x go-function-build
# Build the function as a plugin. Outputs result to 'function.so'
$ go-function-build hello.go
# Upload the function to fission
$ fission function create --name hello --env go-runtime --package function.so
# Map /hello to the hello function
$ fission route create --method GET --url /hello --function hello
# Run the function
$ curl http://$FISSION_ROUTER/hello
Hello, World!
```
+10
View File
@@ -0,0 +1,10 @@
package main
import (
"net/http"
)
func Handler(w http.ResponseWriter, r *http.Request) {
msg := "Hello, World!"
w.Write([]byte(msg))
}
+98
View File
@@ -0,0 +1,98 @@
# Fission Node.js Examples
This directory contains several examples to get you started using Node.js with Fission.
Before running any of these functions, make sure you have created a `nodejs` Fission environment:
```
$ fission env create --name nodejs --image fission/node-env
```
## Function signature
Every Node.js function has the same basic form:
```javascript
module.exports = async function(context) {
return {
status: 200,
body: 'Your body here',
headers: {
'Foo': 'Bar'
}
}
}
```
Since it is an `async` function, you can `await` `Promise`s, as demonstrated in the `stock.js` function.
## hello.js
This is a basic "Hello, World!" example. It simply returns a status of `200` and text body.
### Usage
```bash
# Upload your function code to fission
$ fission function create --name hello --env nodejs --code hello.js
# Map GET /hello to your new function
$ fission route create --method GET --url /hello --function hello
# Run the function.
$ curl http://$FISSION_ROUTER/hello
Hello, world!
```
## hello-callback.js
This is a basic "Hello, World!" example implemented with the legacy callback implementation. If you declare your function with two arguments (`context`, `callback`), a callback taking three arguments (`status`, `body`, `headers`) is provided.
⚠️ Callback support is only provided for backwards compatibility! We recommend that you use `async` functions instead.
### Usage
```bash
# Upload your function code to fission
$ fission function create --name hello-callback --env nodejs --code hello-callback.js
# Map GET /hello-callback to your new function
$ fission route create --method GET --url /hello-callback --function hello-callback
# Run the function.
$ curl http://$FISSION_ROUTER/hello-callback
Hello, world!
```
## stock.js
This is a basic example of how you can easily use asynchronous requests in your functions. By default, the Node.js environment makes the [`request-promise-native`](https://github.com/request/request-promise-native) library available. In this example, the Google Finance API is used to determine when a stock was last traded.
### Usage
```bash
# Upload your function code to fission
$ fission function create --name stock --env nodejs --code stock.js
# Map GET /stock to your new function
$ fission route create --method GET --url /stock --function stock
# Run the function.
$ curl -H "Content-Type: application/json" -X POST -d '{"symbol":"AAPL"}' http://$FISSION_ROUTER/stock
{"text":"AAPL last traded at 138.99"}
```
## kubeEventsSlack.js
This example watches Kubernetes events and sends them to a Slack channel. To use this, create an incoming webhook for your Slack channel, and replace the `slackWebhookPath` in the example code.
### Usage
```bash
# Upload your function code to fission
$ fission fn create --name kubeEventsSlack --env nodejs --code kubeEventsSlack.js
# Watch all services in the default namespace:
$ fission watch create --function kubeEventsSlack --type service --ns default
```
+4
View File
@@ -0,0 +1,4 @@
module.exports = function(context, callback) {
callback(200, "Hello, world!\n");
}
+5 -2
View File
@@ -1,4 +1,7 @@
module.exports = function(context, callback) {
callback(200, "Hello, world!\n");
module.exports = async function(context) {
return {
status: 200,
body: "Hello, world!\n"
};
}
+67
View File
@@ -0,0 +1,67 @@
'use strict';
//
// Watch kubernetes events and send them to a slack channel. This
// uses Slack's incoming webhooks. To use this, create an incoming
// webhook for your slack channel through Slack's UI, and populate the
// relative path below.
//
// Create the function in fission:
//
// fission fn create --name kubeEventsSlack --env nodejs --code kubeEventsSlack.js
//
// Then, watch all services in the default namespace:
//
// fission watch create --function kubeEventsSlack --type service --ns default
//
let https = require('https');
const slackWebhookPath = "YOUR RELATIVE PATH HERE"; // Something like "/services/XXX/YYY/zZz123"
function upcaseFirst(s) {
return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
}
async function sendSlackMessage(msg) {
let postData = `{"text": "${msg}"}`;
let options = {
hostname: "hooks.slack.com",
path: slackWebhookPath,
method: "POST",
headers: {
"Content-Type": "application/json"
}
};
return new Promise(function(resolve, reject) {
let req = https.request(options, function(res) {
console.log(`slack request status = ${res.statusCode}`);
return resolve();
});
req.write(postData);
req.end();
});
}
module.exports = async function(context) {
console.log(context.request.headers);
let obj = context.request.body;
let version = obj.metadata.resourceVersion;
let eventType = context.request.get('X-Kubernetes-Event-Type');
let objType = context.request.get('X-Kubernetes-Object-Type');
let msg = `${upcaseFirst(eventType)} ${objType} ${obj.metadata.name}`;
console.log(msg, version);
if (eventType == 'DELETED' || eventType == 'ADDED') {
console.log("sending event to slack")
await sendSlackMessage(msg);
}
return {
status: 200,
body: ""
}
}
+33 -23
View File
@@ -1,31 +1,41 @@
'use strict';
var http = require('http');
const rp = require('request-promise-native');
module.exports = function (context, callback) {
let body = context.request.body;
console.log(`body text: ${body['text']}`);
module.exports = async function (context) {
const body = context.request.body;
console.log(`body = ${body}`);
const symbol = body['text'].split(' ')[1];
var symbol = body['text'].split(' ')[1];
console.log(`Got symbol: ${symbol}`);
http.get({
host: 'finance.google.com',
path: `/finance/info?q=NYSE:${symbol}`
}, function(response) {
var resp = '';
response.on('data', function(d) {
resp += d;
});
response.on('end', function() {
try {
var parsed = JSON.parse(resp.slice(3));
var lastTrade = parsed[0]['l_cur']
callback(200, `{ "text": "${symbol} last traded at ${lastTrade}" }`);
} catch (e) {
callback(200, `{ "text": "Error (invalid NYSE symbol?)" }`);
if (!symbol) {
return {
status: 400,
body: {
text: 'You must provide a stock symbol.'
}
});
});
};
}
try {
const response = await rp(`http://finance.google.com/finance/info?q=NYSE:${symbol}`);
const parsed = JSON.parse(response.slice(3));
const lastTrade = parsed[0]['l_cur'];
return {
status: 200,
body: {
text: `${symbol} last traded at ${lastTrade}`
},
headers: {
'Content-Type': 'application/json'
}
};
} catch (e) {
console.error(e);
return {
status: 500,
body: e
};
}
}
+3
View File
@@ -0,0 +1,3 @@
<?php
echo "Hello from PHP";
$logger->warning("Hello logger");
+12
View File
@@ -0,0 +1,12 @@
<?php
function handler($context){
/** @var \Psr\Http\Message\ResponseInterface $response */
$response = $context["response"];
/** @var \Psr\Http\Message\ServerRequestInterface $request */
$request = $context["request"];
/** @var \Psr\Log\LoggerInterface $logger */
$logger = $context["logger"];
$response->getBody()->write("Hello from handler PHP");
$logger->warning("Hello logger");
}
+47
View File
@@ -0,0 +1,47 @@
<?php
use \Psr\Http\Message\ResponseInterface;
use \Psr\Http\Message\ServerRequestInterface;
use \Psr\Log\LoggerInterface;
function sendError($response,$message){
$response = $response->withStatus(500);
$response->getBody()->write($message);
return $response;
}
function handler($context){
/** @var ResponseInterface $response */
$response = $context["response"];
/** @var ServerRequestInterface $request */
$request = $context["request"];
/** @var LoggerInterface $logger */
$logger = $context["logger"];
$logger->debug("Request : ",$request->getParsedBody());
if($request->getMethod() != "POST")
return sendError($response,"You must use POST method");
$body = $request->getParsedBody();
if(!isset($body["currency"]))
return sendError($response,"'currency' is not present in the POST request");
$allowedCurrency = ["ltc","btc"];
if(!in_array($body["currency"],$allowedCurrency))
return sendError($response,"'currency' is non allowed. Use one of them : ".implode(",",$allowedCurrency));
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => 'https://api.cryptonator.com/api/ticker/'.$body["currency"].'-usd'
));
$result = curl_exec($curl);
curl_close($curl);
$result = json_decode($result,true);
if($result){
$logger->debug("Response API",$result);
$response->getBody()->write(json_encode(array("text"=>sprintf("%s-USB = %02f",$body["currency"],$result["ticker"]["price"]))));
}else
return sendError($response,"Cryptonator API not available");
}

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