Compare commits

..
Author SHA1 Message Date
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
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
125 changed files with 6033 additions and 348 deletions
+10 -2
View File
@@ -1,4 +1,12 @@
fission-bundle/fission-bundle
# Pycharm IDE
.idea
environments/php7/vendor/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Pycharm IDE
.idea
vendor/
+3 -3
View File
@@ -3,7 +3,7 @@ dist: trusty
language: go
go:
- 1.7
- 1.8
- tip
install:
@@ -16,5 +16,5 @@ script:
- ./fission-bundle/build.sh
- hack/verify-gofmt.sh
- /tmp/test-etcd/etcd &
- go test -v -i $(go list ./... | grep -v '/vendor/')
- go test -v $(go list ./... | grep -v '/vendor/')
- 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')
+19 -1
View File
@@ -110,4 +110,22 @@ be loaded with any function.
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.
then requests the container to load the function.
Logger
-----------
Logger helps to forward function logs to centralized db service for log
persistence. Currently only influxdb is supported to store logs.
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.
+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
+205 -45
View File
@@ -37,6 +37,11 @@ container.
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
@@ -97,71 +102,226 @@ Roughly in order of priority:
### User stories
#### Compiled language
#### Environment Creation
User writes a function in Go.
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.
```
$ fission function create --code blah.go
$ cat golang.yaml
type: Environment
metadata:
name: go
spec:
runtimeImage: fission/go-runtime
builderImage: fission/go-builder
buildCommand:
- "/build.sh"
fileExtentions:
- go
<compilation errors>
<user edits file>
$ $EDITOR blah.go
<fixes errors>
$ fission function update --code blah.go
<success>
$ fission env create -f golang.yaml
```
(Or perhaps we could have a `fission function check --env x --code y`
which just does compilation, without creating a function object?
Useful for integration into IDEs. Basically, just an on-demand
builder. Useful when you don't wanna setup anything on your laptop.)
#### 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
#### Collections of files
We should probably have a manifest in YAML/JSON/etc syntax for
specifying a function. We could also use that YAML to let users
specify the function's environment, resource requirements, etc.
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 funcion create -f blah.yaml
$ fission function create --deployment-package foo.zip
$ cat blah.yaml
type: Function
metadata:
name: ...
environment: ...
files:
- foo.py
- bar.py
- baz/*.py
```
$ fission route ... # routes work as usual
```
The yaml file could specify a list of files. The fission client would
deal with packaging up this set of files and uploading the package.
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
A manifest could point at a function's dependency spec. The
Environment's "builder" container could then fetch these deps.
The source package of a function can contain dependency specs.
So a NodeJS function manifest could contain a reference to
package.json. A "builder" container in the NodeJS environment would
then run npm on that function.
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.
``` $ cat func.yaml
type: Function
metadata: ...
environment: ...
dependencies: package.json
$ fission function create -f func.yaml
```
$ 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.
+59 -3
View File
@@ -7,6 +7,8 @@
* [Get and Run Fission: GKE or other Cloud](#get-and-run-fission-gke-or-other-cloud)
* [Install the client CLI](#install-the-client-cli)
* [Run an example](#run-an-example)
* [Enable Persistent Function Logs (Optional)](#enable-persistent-function-logs-optional)
* [Use the web based Fission-ui (Optional)](#use-the-web-based-fission-ui-optional)
## Running Fission on your Cluster
@@ -17,14 +19,14 @@ You can install Kubernetes on your laptop with [minikube](https://github.com/kub
#### 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.14.0/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /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.14.0/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /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
```
@@ -80,6 +82,18 @@ svc```). Then:
$ 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¬
```
### Install the client CLI
Get the CLI binary for Mac:
@@ -101,7 +115,7 @@ Finally, you're ready to use Fission!
```
$ fission env create --name nodejs --image fission/node-env
$ echo 'module.exports = function(context, callback) { callback(200, "Hello, world!\n"); }' > hello.js
$ curl https://raw.githubusercontent.com/fission/fission/master/examples/nodejs/hello.js > hello.js
$ fission function create --name hello --env nodejs --code hello.js
@@ -110,3 +124,45 @@ Finally, you're ready to use Fission!
$ 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
# After Fission 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).
+48 -9
View File
@@ -63,7 +63,7 @@ Usage
$ fission env create --name nodejs --image fission/node-env
# A javascript one-liner that prints "hello world"
$ echo 'module.exports = function(context, callback) { callback(200, "Hello, world!\n"); }' > hello.js
$ 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
@@ -88,14 +88,14 @@ You can install Kubernetes on your laptop with [minikube](https://github.com/kub
#### 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.14.0/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /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.14.0/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /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
```
@@ -150,6 +150,37 @@ svc```). Then:
$ export FISSION_URL=http://$(kubectl --namespace fission get svc controller -o=jsonpath='{..ip}')
$ export FISSION_ROUTER=$(kubectl --namespace fission get svc router -o=jsonpath='{..ip}')
```
### Get and run fission: OpenShift
If you're using OpenShift, it's possible to run Fission on it! The deployment
template needs to be deployed as a user with cluster-admin permissions (like `system:admin`), as it needs to create a `ClusterRole` for deploying function containers from the `fission` namespace/project.
Identically as with Kubernetes, you need to set the FISSION_URL and FISSION_ROUTER environment variables. If you're using minishift, use these commands:
```
$ export FISSION_URL=http://$(minishift ip):31313¬
$ export FISSION_ROUTER=$(minishift ip):31314¬
```
#### Using Minishift or Local Cluster
If you're using minishift or no cloud provider, use these commands to set up services with NodePort. This exposes fission on ports 31313 and 31314.
```
$ oc login -u system:admin
$ oc create -f http://fission.io/fission-openshift.yaml
$ oc create -f http://fission.io/fission-nodeport.yaml
```
#### Using other clouds
If you're using any cloud provider that supports the LoadBalancer service type, use these commands:
```
$ oc login -u system:admin
$ oc create -f http://fission.io/fission-openshift.yaml
$ oc create -f http://fission.io/fission-cloud.yaml
```
After these steps, you should be able to run fission client as with kubernetes.
### Install the client CLI
@@ -172,7 +203,7 @@ Finally, you're ready to use Fission!
```
$ fission env create --name nodejs --image fission/node-env
$ echo 'module.exports = function(context, callback) { callback(200, "Hello, world!\n"); }' > hello.js
$ curl https://raw.githubusercontent.com/fission/fission/master/examples/nodejs/hello.js > hello.js
$ fission function create --name hello --env nodejs --code hello.js
@@ -182,6 +213,9 @@ Finally, you're ready to use Fission!
Hello, world!
```
You can also set up persistence for logs: [instructions here](INSTALL.md).
Compiling Fission
=================
@@ -189,15 +223,19 @@ Compiling Fission
just deploying Fission, use fission.yaml which points to prebuilt
images.]
You'll need go installed, along with the glide dependecy management
tool. You'll also need docker for building images.
You'll need go installed, along with the [glide dependency management
tool](https://github.com/Masterminds/glide#install).
You'll also need docker for building images.
The server side is compiled as one binary ("fission-bundle") which
contains controller, poolmgr and router; it invokes the right one
based on command-line arguments.
To build fission-bundle: clone this repo, then from the top level
directory:
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
@@ -207,7 +245,8 @@ directory:
$ pushd fission-bundle
$ ./build.sh
# Edit push.sh to point to your registry
# Edit push.sh to point to your registry, or comment out the `docker push`
# line if building into your local minikube for dev purposes
$ $EDITOR push.sh
$ ./push.sh
$ popd
+2 -2
View File
@@ -17,10 +17,10 @@ limitations under the License.
package cache
import (
"time"
"errors"
"fmt"
"time"
"github.com/fission/fission"
)
+5 -3
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 {
+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
+46 -7
View File
@@ -21,25 +21,37 @@ 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/fission/fission"
"github.com/fission/fission/fission/logdb"
)
type API struct {
FunctionStore
HTTPTriggerStore
EnvironmentStore
WatchStore
}
type (
API struct {
FunctionStore
HTTPTriggerStore
TimeTriggerStore
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},
EnvironmentStore: EnvironmentStore{ResourceStore: *rs},
WatchStore: WatchStore{ResourceStore: *rs},
}
@@ -47,6 +59,7 @@ func MakeAPI(rs *ResourceStore) *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
@@ -61,7 +74,25 @@ 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) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
fmt.Fprintf(w, "{\"message\": \"Fission API\", \"version\": \"0.1.0\"}\n")
}
@@ -93,6 +124,14 @@ func (api *API) Serve(port int) {
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("/proxy/{dbType}", api.FunctionLogsApiPost).Methods("POST")
address := fmt.Sprintf(":%v", port)
log.WithFields(log.Fields{"port": port}).Info("Server started")
+175 -1
View File
@@ -24,8 +24,8 @@ 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/fission/fission"
@@ -36,6 +36,27 @@ 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
@@ -201,10 +312,16 @@ func TestWatchApi(t *testing.T) {
},
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
@@ -221,6 +338,53 @@ func TestWatchApi(t *testing.T) {
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()
@@ -232,6 +396,7 @@ func TestMain(m *testing.M) {
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})
@@ -241,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)
+125 -31
View File
@@ -26,8 +26,6 @@ import (
"net/http"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/fission/fission"
)
@@ -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 {
@@ -472,7 +457,7 @@ func (c *Client) WatchCreate(w *fission.Watch) (*fission.Metadata, error) {
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
body, err := c.handleCreateResponse(resp)
if err != nil {
return nil, err
}
@@ -532,6 +517,7 @@ func (c *Client) WatchList() ([]fission.Watch, error) {
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
@@ -546,3 +532,111 @@ func (c *Client) WatchList() ([]fission.Watch, error) {
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
}
+4 -2
View File
@@ -21,8 +21,8 @@ import (
"io/ioutil"
"net/http"
log "github.com/Sirupsen/logrus"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"github.com/fission/fission"
)
@@ -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
+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")
}
+38 -3
View File
@@ -17,14 +17,16 @@ 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/fission/fission"
)
@@ -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)
}
+21 -2
View File
@@ -17,7 +17,7 @@ limitations under the License.
package controller
import (
log "github.com/Sirupsen/logrus"
log "github.com/sirupsen/logrus"
"github.com/fission/fission"
)
@@ -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) {
+18 -1
View File
@@ -21,8 +21,8 @@ import (
"io/ioutil"
"net/http"
log "github.com/Sirupsen/logrus"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"github.com/fission/fission"
)
@@ -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
+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
}
+3 -1
View File
@@ -21,8 +21,8 @@ import (
"io/ioutil"
"net/http"
log "github.com/Sirupsen/logrus"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"github.com/fission/fission"
)
@@ -47,6 +47,7 @@ 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
@@ -71,6 +72,7 @@ func (api *API) WatchApiCreate(w http.ResponseWriter, r *http.Request) {
return
}
w.WriteHeader(http.StatusCreated)
api.respondWithSuccess(w, resp)
}
+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 -5
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,12 +92,32 @@ app.all('/', function (req, res) {
}
res.status(status).send(body);
}
try {
userFunction(context, callback);
} catch(e) {
console.log(`Function error: ${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();
}
}
?>
+4 -3
View File
@@ -1,8 +1,9 @@
FROM ubuntu:16.04
FROM alpine:3.5
RUN apt-get update -y
RUN apt-get install -y python3 python3-pip python3-dev build-essential
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
+1 -1
View File
@@ -41,5 +41,5 @@ 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 parmeter set to the
After this, fission functions that have the env parameter set to the
same environment name as this command will use this environment.
+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
+41
View File
@@ -0,0 +1,41 @@
# Fission: Ruby Environment
This is the Ruby environment for Fission.
It's a Docker image containing a Ruby 2.4.1 runtime, along with a
dynamic loader. A few common dependencies are included in the
Gemfile.
## 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.
+66
View File
@@ -0,0 +1,66 @@
# frozen_string_literal: true
require 'rack'
CODEPATH = '/userfunc/user'
module Fission
class Request < Rack::Request
def headers
Hash[
*env.select { |k,v| k.start_with?('HTTP_') }
.map { |k,v| [k.sub(/\AHTTP_/, '').split('_').map(&:capitalize).join('-'), v] }
.sort
.flatten
]
end
end
class Context
attr_reader :env
def initialize(env)
@env = env
end
def request
@request ||= Request.new(env)
end
end
module Specializer
def self.call(env)
load CODEPATH
Rack::Response.new([], 201).finish
rescue
Rack::Response.new(['500 Internal Server Error'], 500, {}).finish
end
end
module Handler
def self.call(env)
response = if method(:handler).arity > 0
handler(Context.new(env))
else
handler
end
response.is_a?(Array) ? response : Rack::Response.new([response]).finish
rescue
Rack::Response.new(['500 Internal Server Error'], 500, {}).finish
end
end
end
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");
}
+1 -1
View File
@@ -16,7 +16,7 @@ def main():
items = [("<li>%s</li>" % escape(m.decode('utf-8'))) for m in messages]
ul = "<ul>%s</ul>" % "\n".join(items)
return """
<html><body>
<html><body style="font-family:sans-serif;font-size:2rem;padding:40px">
<h1>Guestbook</h1>
<form action="/guestbook" method="POST">
<input type="text" name="text">
+33
View File
@@ -0,0 +1,33 @@
# frozen_string_literal: true
require 'net/http'
require 'uri'
require 'json'
SLACK_BASE_URL = 'https://hooks.slack.com/'
SLACK_WEBHOOK_PATH = 'YOUR RELATIVE PATH HERE' # Something like "/services/XXX/YYY/zZz123"
def send_slack_message(message)
uri = URI.join(SLACK_BASE_URL, SLACK_WEBHOOK_PATH)
data = "{'channel': '#hackdays-serverless', 'username': 'fissionbot', 'text': \"#{message}\", 'icon_emoji': ':fission:'}"
res = Net::HTTP.post_form(uri, payload: data)
res.success?
end
def handler(context)
request = context.request
event_type = request.headers['X-Kubernetes-Event-Type']
object_type = request.headers['X-Kubernetes-Object-Type']
object = JSON.parse(request.body.read)
object_name = object.dig('metadata', 'name')
object_namespace = object.dig('metadata', 'namespace')
message = "#{event_type} #{object_type} #{object_namespace}/#{object_name}"
if send_slack_message(message)
"Slack message sent - #{message}"
else
Rack::Response.new(["Failed to send Slack message - #{message}"], 500).finish
end
end
+4
View File
@@ -0,0 +1,4 @@
# frozen_string_literal: true
def handler
"Hello, world!\n"
end
+19
View File
@@ -0,0 +1,19 @@
# frozen_string_literal: true
def handler(context)
request = context.request
msg = <<~MSG
---ENV---
#{request.env.map { |h| h.join('=') }.join("\n") }
---HEADERS---
#{request.headers.map { |h| h.join(': ') }.join("\n") }
---PARAMS---
#{request.params.map { |h| h.join('=') }.join("\n") }
--BODY--
#{request.body.read}
MSG
Rack::Response.new([msg]).finish
end
+1
View File
@@ -1 +1,2 @@
#!/bin/sh
GOOS=linux GOARCH=386 go build
+29
View File
@@ -7,13 +7,18 @@ import (
"github.com/docopt/docopt-go"
"github.com/fission/fission/controller"
"github.com/fission/fission/kubewatcher"
"github.com/fission/fission/logger"
"github.com/fission/fission/poolmgr"
"github.com/fission/fission/router"
"github.com/fission/fission/timer"
)
func runController(port int, etcdUrl string, filepath string) {
// filePath will be created if it doesn't exist.
fileStore := controller.MakeFileStore(filepath)
if fileStore == nil {
log.Fatalf("Failed to initialize filestore")
}
rs, err := controller.MakeResourceStore(fileStore, []string{etcdUrl})
if err != nil {
@@ -44,6 +49,18 @@ func runKubeWatcher(controllerUrl, routerUrl string) {
}
}
func runLogger() {
logger.Start()
log.Fatalf("Error: Logger exited.")
}
func runTimer(controllerUrl, routerUrl string) {
err := timer.Start(controllerUrl, routerUrl)
if err != nil {
log.Fatalf("Error starting timer: %v", err)
}
}
func getPort(portArg interface{}) int {
portArgStr := portArg.(string)
port, err := strconv.Atoi(portArgStr)
@@ -77,6 +94,8 @@ Usage:
fission-bundle --routerPort=<port> [--controllerUrl=<url> --poolmgrUrl=<url>]
fission-bundle --poolmgrPort=<port> [--controllerUrl=<url> --namespace=<namespace>]
fission-bundle --kubewatcher [--controllerUrl=<url> --routerUrl=<url>]
fission-bundle --logger
fission-bundle --timer [--controllerUrl=<url> --routerUrl=<url>]
Options:
--controllerPort=<port> Port that the controller should listen on.
--routerPort=<port> Port that the router should listen on.
@@ -88,6 +107,8 @@ Options:
--filepath=<filepath> Directory to store functions in.
--namespace=<namespace> Kubernetes namespace in which to run function containers. Defaults to 'fission-function'.
--kubewatcher Start Kubernetes events watcher.
--logger Start logger.
--timer Start Timer.
`
arguments, err := docopt.Parse(usage, nil, true, "fission-bundle", false)
if err != nil {
@@ -120,5 +141,13 @@ Options:
runKubeWatcher(controllerUrl, routerUrl)
}
if arguments["--logger"] == true {
runLogger()
}
if arguments["--timer"] == true {
runTimer(controllerUrl, routerUrl)
}
select {}
}
+19
View File
@@ -30,3 +30,22 @@ spec:
nodePort: 31313
selector:
svc: controller
---
# If you want to switch to external db service, please checkout
# sample Kubernetes Service config in fission-logger.yaml.
apiVersion: v1
kind: Service
metadata:
name: influxdb
namespace: fission
labels:
svc: influxdb
spec:
type: LoadBalancer
ports:
- port: 8086
targetPort: 8086
nodePort: 31315
selector:
svc: influxdb
+122
View File
@@ -0,0 +1,122 @@
# Sample Kubernetes Service config for external db service.
#
# apiVersion: v1
# kind: Service
# metadata:
# name: influxdb
# namespace: fission
# spec:
# ports:
# - name: "8086"
# port: 8086
# targetPort: 8086
# protocol: TCP
# ---
# apiVersion: v1
# kind: Endpoints
# metadata:
# name: influxdb
# namespace: fission
# subsets:
# - addresses:
# - ip: replace.influxdb.host.here
# ports:
# - name: "8086"
# port: 8086
# protocol: TCP
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: influxdb
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: influxdb
spec:
containers:
- name: influxdb
image: tutum/influxdb
env:
- name: PRE_CREATE_DB
value: fissionFunctionLog
# Create a random username/password for InfluxDB here, and
# repeat it in the fluentd spec below.
- name: ADMIN_USER
value: ""
- name: INFLUXDB_INIT_PWD
value: ""
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: logger
namespace: fission
spec:
template:
metadata:
labels:
svc: logger
spec:
containers:
- name: logger
image: fission/fission-bundle
imagePullPolicy: IfNotPresent
command: ["/fission-bundle"]
args: ["--logger"]
volumeMounts:
- name: container-log
mountPath: /var/log/containers
readOnly: true
- name: docker-log
mountPath: /var/lib/docker/containers
readOnly: true
- name: fission-log
mountPath: /var/log/fission
readOnly: false
ports:
- containerPort: 1234
hostPort: 1234
protocol: TCP
- name: fluentd
image: fission/fluentd
imagePullPolicy: IfNotPresent
env:
- name: INFLUXDB_ADDRESS
value: influxdb
- name: INFLUXDB_PORT
value: "8086"
- name: INFLUXDB_DBNAME
value: "fissionFunctionLog"
# Username/password for fluentd to push logs to
# influxdb. This should match the influxdb
# username/passowrd defined above.
- name: INFLUXDB_USERNAME
value: ""
- name: INFLUXDB_PASSWD
value: ""
volumeMounts:
- name: container-log
mountPath: /var/log/containers
readOnly: true
- name: docker-log
mountPath: /var/lib/docker/containers
readOnly: true
- name: fission-log
mountPath: /var/log/fission
readOnly: false
volumes:
- name: container-log
hostPath:
path: /var/log/containers
- name: docker-log
hostPath:
path: /var/lib/docker/containers
- name: fission-log
hostPath:
path: /var/log/fission
+19
View File
@@ -30,3 +30,22 @@ spec:
nodePort: 31313
selector:
svc: controller
---
# If you want to switch to external db service, please checkout
# sample Kubernetes Service config in fission-logger.yaml.
apiVersion: v1
kind: Service
metadata:
name: influxdb
namespace: fission
labels:
svc: influxdb
spec:
type: NodePort
ports:
- port: 8086
targetPort: 8086
nodePort: 31315
selector:
svc: influxdb
+202
View File
@@ -0,0 +1,202 @@
---
apiVersion: v1
kind: ProjectRequest
metadata:
name: fission
labels:
name: fission
---
apiVersion: v1
kind: ProjectRequest
metadata:
name: fission-function
labels:
name: fission-function
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: poolmgr
namespace: fission
---
apiVersion: v1
kind: ClusterRole
metadata:
name: fission:poolmgr
rules:
- apiGroups:
- extensions
attributeRestrictions: null
resources:
- deployments
verbs:
- create
- get
- list
- update
- watch
- apiGroups:
- ""
attributeRestrictions: null
resources:
- pods
verbs:
- get
- list
- update
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: controller
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: controller
spec:
volumes:
- name: controller-storage
emptyDir: {}
containers:
- name: controller
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--controllerPort", "8888", "--filepath", "/filestore"]
volumeMounts:
- name: controller-storage
mountPath: /filestore
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: router
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: router
spec:
containers:
- name: router
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--routerPort", "8888"]
---
apiVersion: v1
kind: Service
metadata:
name: poolmgr
namespace: fission
labels:
svc: poolmgr
spec:
ports:
- port: 80
targetPort: 8888
selector:
svc: poolmgr
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: poolmgr
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: poolmgr
spec:
containers:
- name: poolmgr
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--poolmgrPort", "8888"]
serviceAccount: poolmgr
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: kubewatcher
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: kubewatcher
spec:
containers:
- name: kubewatcher
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--kubewatcher"]
---
apiVersion: v1
kind: Service
metadata:
name: etcd
namespace: fission
labels:
svc: etcd
spec:
ports:
- port: 2379
targetPort: 2379
selector:
svc: etcd
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: etcd
namespace: fission
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
- name: ETCD_DATA_DIR
value: /tmp/default.etcd
---
apiVersion: v1
groupNames: null
kind: RoleBinding
metadata:
name: fission:poolmgr
namespace: fission-function
roleRef:
name: fission:poolmgr
subjects:
- kind: ServiceAccount
name: poolmgr
namespace: fission
userNames:
- system:serviceaccount:fission:poolmgr
+174
View File
@@ -0,0 +1,174 @@
apiVersion: v1
kind: Namespace
metadata:
name: fission
labels:
name: fission
---
apiVersion: v1
kind: Namespace
metadata:
name: fission-function
labels:
name: fission-function
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: fission-svc
namespace: fission
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1alpha1
metadata:
name: fission-admin
namespace: fission
subjects:
- kind: ServiceAccount
name: fission-svc
namespace: fission
roleRef:
kind: ClusterRole
name: admin
apiGroup: rbac.authorization.k8s.io
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1alpha1
metadata:
name: fission-function-admin
namespace: fission-function
subjects:
- kind: ServiceAccount
name: fission-svc
namespace: fission
roleRef:
kind: ClusterRole
name: admin
apiGroup: rbac.authorization.k8s.io
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: controller
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: controller
spec:
containers:
- name: controller
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--controllerPort", "8888", "--filepath", "/filestore"]
serviceAccountName: fission-svc
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: router
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: router
spec:
containers:
- name: router
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--routerPort", "8888"]
serviceAccountName: fission-svc
---
apiVersion: v1
kind: Service
metadata:
name: poolmgr
namespace: fission
labels:
svc: poolmgr
spec:
ports:
- port: 80
targetPort: 8888
selector:
svc: poolmgr
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: poolmgr
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: poolmgr
spec:
containers:
- name: poolmgr
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--poolmgrPort", "8888"]
serviceAccountName: fission-svc
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: kubewatcher
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: kubewatcher
spec:
containers:
- name: kubewatcher
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--kubewatcher"]
serviceAccountName: fission-svc
---
apiVersion: v1
kind: Service
metadata:
name: etcd
namespace: fission
labels:
svc: etcd
spec:
ports:
- port: 2379
targetPort: 2379
selector:
svc: etcd
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: etcd
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: etcd
spec:
containers:
- name: etcd
image: quay.io/coreos/etcd
imagePullPolicy: IfNotPresent
env:
- name: ETCD_LISTEN_CLIENT_URLS
value: http://0.0.0.0:2379
- name: ETCD_ADVERTISE_CLIENT_URLS
value: http://etcd:2379
serviceAccountName: fission-svc
+24 -4
View File
@@ -28,7 +28,7 @@ spec:
spec:
containers:
- name: controller
image: fission/fission-bundle:alpha10
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--controllerPort", "8888", "--filepath", "/filestore"]
@@ -47,7 +47,7 @@ spec:
spec:
containers:
- name: router
image: fission/fission-bundle:alpha10
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--routerPort", "8888"]
@@ -81,7 +81,7 @@ spec:
spec:
containers:
- name: poolmgr
image: fission/fission-bundle:alpha10
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--poolmgrPort", "8888"]
@@ -100,10 +100,29 @@ spec:
spec:
containers:
- name: kubewatcher
image: fission/fission-bundle:alpha10
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--kubewatcher"]
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: timer
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: timer
spec:
containers:
- name: timer
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--timer"]
---
apiVersion: v1
kind: Service
@@ -135,6 +154,7 @@ spec:
containers:
- name: etcd
image: quay.io/coreos/etcd
imagePullPolicy: IfNotPresent
env:
- name: ETCD_LISTEN_CLIENT_URLS
value: http://0.0.0.0:2379
+6 -1
View File
@@ -35,7 +35,12 @@ func getClient(serverUrl string) *client.Client {
fatal("Need --server or FISSION_URL set to your fission server.")
}
serverUrl = "http://" + strings.TrimPrefix(serverUrl, "http://")
isHTTPS := strings.Index(serverUrl, "https://") == 0
isHTTP := strings.Index(serverUrl, "http://") == 0
if !(isHTTP || isHTTPS) {
serverUrl = "http://" + serverUrl
}
return client.MakeClient(serverUrl)
}
+26 -3
View File
@@ -18,19 +18,26 @@ package main
import (
"fmt"
"os"
"text/tabwriter"
"github.com/urfave/cli"
"github.com/fission/fission"
"os"
)
func envCreate(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
envName := c.String("name")
if len(envName) == 0 {
fatal("Need a name, use --name.")
}
envImg := c.String("image")
if len(envImg) == 0 {
fatal("Need an image, use --image.")
}
env := &fission.Environment{
Metadata: fission.Metadata{
@@ -50,6 +57,10 @@ func envGet(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
envName := c.String("name")
if len(envName) == 0 {
fatal("Need a name, use --name.")
}
m := &fission.Metadata{Name: envName}
env, err := client.EnvironmentGet(m)
checkErr(err, "get environment")
@@ -66,12 +77,20 @@ func envUpdate(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
envName := c.String("name")
image := c.String("image")
if len(envName) == 0 {
fatal("Need a name, use --name.")
}
envImg := c.String("image")
if len(envImg) == 0 {
fatal("Need an image, use --image.")
}
env := &fission.Environment{
Metadata: fission.Metadata{
Name: envName,
},
RunContainerImageUrl: image,
RunContainerImageUrl: envImg,
}
_, err := client.EnvironmentUpdate(env)
@@ -85,6 +104,10 @@ func envDelete(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
envName := c.String("name")
if len(envName) == 0 {
fatal("Need a name , use --name.")
}
m := &fission.Metadata{Name: envName}
err := client.EnvironmentDelete(m)
checkErr(err, "delete environment")
+161 -6
View File
@@ -17,18 +17,51 @@ limitations under the License.
package main
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"strings"
"text/tabwriter"
"time"
"github.com/satori/go.uuid"
"github.com/urfave/cli"
"github.com/fission/fission"
"github.com/fission/fission/fission/logdb"
)
func fnFetchCode(filePath string) []byte {
var code []byte
var err error
if strings.HasPrefix(filePath, "http://") || strings.HasPrefix(filePath, "https://") {
var resp *http.Response
resp, err = http.Get(filePath)
if err != nil {
checkErr(err, fmt.Sprintf("download function"))
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("%v - HTTP response returned non 200 status", resp.StatusCode)
checkErr(err, fmt.Sprintf("download function"))
}
code, err = ioutil.ReadAll(resp.Body)
if err != nil {
checkErr(err, fmt.Sprintf("download function body %v", filePath))
}
} else {
code, err = ioutil.ReadFile(filePath)
checkErr(err, fmt.Sprintf("read %v", filePath))
}
return code
}
func fnCreate(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
@@ -44,11 +77,13 @@ func fnCreate(c *cli.Context) error {
fileName := c.String("code")
if len(fileName) == 0 {
fatal("Need --code argument.")
fileName = c.String("package")
if len(fileName) == 0 {
fatal("Need --code or --package argument.")
}
}
code, err := ioutil.ReadFile(fileName)
checkErr(err, fmt.Sprintf("read %v", fileName))
code := fnFetchCode(fileName)
function := &fission.Function{
Metadata: fission.Metadata{Name: fnName},
@@ -56,7 +91,7 @@ func fnCreate(c *cli.Context) error {
Code: string(code),
}
_, err = client.FunctionCreate(function)
_, err := client.FunctionCreate(function)
checkErr(err, "create function")
fmt.Printf("function '%v' created\n", fnName)
@@ -109,6 +144,10 @@ func fnGetMeta(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
fnName := c.String("name")
if len(fnName) == 0 {
fatal("Need name of function, use --name")
}
fnUid := c.String("uid")
m := &fission.Metadata{Name: fnName, Uid: fnUid}
@@ -127,6 +166,9 @@ func fnUpdate(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
fnName := c.String("name")
if len(fnName) == 0 {
fatal("Need name of function, use --name")
}
function, err := client.FunctionGet(&fission.Metadata{Name: fnName})
checkErr(err, fmt.Sprintf("read function '%v'", fnName))
@@ -138,8 +180,7 @@ func fnUpdate(c *cli.Context) error {
fileName := c.String("code")
if len(fileName) > 0 {
code, err := ioutil.ReadFile(fileName)
checkErr(err, fmt.Sprintf("read %v", fileName))
code := fnFetchCode(fileName)
function.Code = string(code)
}
@@ -238,3 +279,117 @@ func fnEdit(c *cli.Context) error {
fmt.Printf("function %v updated, new uuid: %v\n", newfn.Name, newfn.Uid)
return nil
}
func fnLogs(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
fnName := c.String("name")
if len(fnName) == 0 {
fatal("Need name of function, use --name")
}
dbType := c.String("dbtype")
if len(dbType) == 0 {
dbType = logdb.INFLUXDB
}
fnPod := c.String("pod")
m := &fission.Metadata{Name: fnName}
f, err := client.FunctionGet(m)
checkErr(err, "get function")
// client first send db query to controller, then controller will
// establishe a proxy server that bridges the client and the database.
logDB, err := logdb.GetLogDB(dbType, c.GlobalString("server"))
if err != nil {
fatal("failed to connect log database")
}
requestChan := make(chan struct{})
responseChan := make(chan struct{})
ctx := context.Background()
go func(ctx context.Context, requestChan, responseChan chan struct{}) {
t := time.Unix(0, 0*int64(time.Millisecond))
for {
select {
case <-requestChan:
logFilter := logdb.LogFilter{
Pod: fnPod,
Function: f.Metadata.Name,
FuncUid: f.Metadata.Uid,
Since: t,
}
logEntries, err := logDB.GetLogs(logFilter)
if err != nil {
fatal("failed to query logs")
}
for _, logEntry := range logEntries {
if c.Bool("d") {
fmt.Printf("Timestamp: %s\nNamespace: %s\nFunction Name: %s\nFunction ID: %s\nPod: %s\nContainer: %s\nStream: %s\nLog: %s\n---\n",
logEntry.Timestamp, logEntry.Namespace, logEntry.FuncName, logEntry.FuncUid, logEntry.Pod, logEntry.Container, logEntry.Stream, logEntry.Message)
} else {
fmt.Printf("[%s] %s\n", logEntry.Timestamp, logEntry.Message)
}
t = logEntry.Timestamp
}
responseChan <- struct{}{}
case <-ctx.Done():
return
}
}
}(ctx, requestChan, responseChan)
for {
requestChan <- struct{}{}
<-responseChan
if !c.Bool("f") {
ctx.Done()
return nil
}
time.Sleep(1 * time.Second)
}
}
func fnPods(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
fnName := c.String("name")
if len(fnName) == 0 {
fatal("Need name of function, use --name")
}
dbType := c.String("dbtype")
if len(dbType) == 0 {
dbType = logdb.INFLUXDB
}
m := &fission.Metadata{Name: fnName}
f, err := client.FunctionGet(m)
checkErr(err, "get function")
// client first sends db query to the controller, then the controller
// will establish a proxy server that bridges the client and the database.
logDB, err := logdb.GetLogDB(dbType, c.GlobalString("server"))
if err != nil {
fatal("failed to connect log database")
}
logFilter := logdb.LogFilter{
Function: f.Metadata.Name,
FuncUid: f.Metadata.Uid,
}
pods, err := logDB.GetPods(logFilter)
if err != nil {
fatal("failed to get pods of function")
return err
}
fmt.Printf("NAME\t\n")
for _, pod := range pods {
fmt.Println(pod)
}
return err
}
+158
View File
@@ -0,0 +1,158 @@
/*
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 logdb
import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"strings"
"time"
influxdbClient "github.com/influxdata/influxdb/client/v2"
"github.com/fission/fission"
)
const (
INFLUXDB_DATABASE = "fissionFunctionLog"
INFLUXDB_URL = "http://influxdb:8086/query"
)
func NewInfluxDB(serverURL string) (InfluxDB, error) {
return InfluxDB{endpoint: serverURL}, nil
}
type InfluxDB struct {
endpoint string
}
func (influx InfluxDB) GetPods(filter LogFilter) ([]string, error) {
parameters := make(map[string]interface{})
parameters["funcuid"] = filter.FuncUid
queryCmd := "select * from \"log\" where \"funcuid\" = $funcuid group by \"pod\""
query := influxdbClient.NewQueryWithParameters(queryCmd, INFLUXDB_DATABASE, "", parameters)
response, err := influx.query(query)
if err != nil /*|| response.Err != ""*/ {
return []string{}, err
}
pods := []string{}
for _, r := range response.Results {
for _, series := range r.Series {
for _, pod := range series.Tags {
pods = append(pods, pod)
}
}
}
return pods, nil
}
func (influx InfluxDB) GetLogs(filter LogFilter) ([]LogEntry, error) {
timestamp := filter.Since.UnixNano()
var queryCmd string
// please check "Example 4: Bind a parameter in the WHERE clause to specific tag value"
// at https://docs.influxdata.com/influxdb/v1.2/tools/api/
parameters := make(map[string]interface{})
parameters["funcuid"] = filter.FuncUid
parameters["time"] = timestamp
if filter.Pod != "" {
queryCmd = "select * from \"log\" where \"funcuid\" = $funcuid AND \"pod\" = $pod AND \"time\" > $time ORDER BY time ASC"
parameters["pod"] = filter.Pod
} else {
queryCmd = "select * from \"log\" where \"funcuid\" = $funcuid AND \"time\" > $time ORDER BY time ASC"
}
query := influxdbClient.NewQueryWithParameters(queryCmd, INFLUXDB_DATABASE, "", parameters)
logEntries := []LogEntry{}
response, err := influx.query(query)
if err != nil {
return logEntries, nil
}
for _, r := range response.Results {
for _, series := range r.Series {
for _, row := range series.Values {
t, err := time.Parse(time.RFC3339, row[0].(string))
if err != nil {
log.Fatal(err)
}
logEntries = append(logEntries, LogEntry{
Timestamp: t,
Container: row[2].(string),
FuncName: row[3].(string),
FuncUid: row[4].(string),
Message: strings.TrimSuffix(row[5].(string), "\n"),
Namespace: row[6].(string),
Pod: row[7].(string),
Stream: row[8].(string),
})
}
}
}
return logEntries, nil
}
func (influx InfluxDB) query(query influxdbClient.Query) (*influxdbClient.Response, error) {
queryURL, err := url.Parse(influx.endpoint)
if err != nil {
return nil, err
}
// connect to controller first, then controller will redirect our query command
// to influxdb and proxy back the db response.
queryURL.Path = fmt.Sprintf("/proxy/%s", INFLUXDB)
req, err := http.NewRequest("POST", queryURL.String(), nil)
if err != nil {
return nil, err
}
parametersBytes, err := json.Marshal(query.Parameters)
if err != nil {
return nil, err
}
// set up http URL query string
params := req.URL.Query()
params.Set("q", query.Command)
params.Set("db", query.Database)
params.Set("params", string(parametersBytes))
req.URL.RawQuery = params.Encode()
httpClient := http.Client{Timeout: 5 * time.Second}
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fission.MakeErrorFromHTTP(resp)
}
// decode influxdb response
response := influxdbClient.Response{}
decoder := json.NewDecoder(resp.Body)
decoder.UseNumber()
if decoder.Decode(&response) != nil {
return nil, fmt.Errorf("Failed to decode influxdb response: %v", err)
}
return &response, nil
}
+59
View File
@@ -0,0 +1,59 @@
/*
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 logdb
import (
"time"
log "github.com/sirupsen/logrus"
)
const (
INFLUXDB = "influxdb"
)
type LogDatabase interface {
GetPods(LogFilter) ([]string, error)
GetLogs(LogFilter) ([]LogEntry, error)
}
type LogFilter struct {
Pod string
Function string
FuncUid string
Since time.Time
}
type LogEntry struct {
Timestamp time.Time
Message string
Stream string
Container string
Namespace string
FuncName string
FuncUid string
Pod string
}
func GetLogDB(dbType string, serverURL string) (LogDatabase, error) {
switch dbType {
case INFLUXDB:
return NewInfluxDB(serverURL)
}
log.Fatalf("Log database type is incorrect, now only support %s", INFLUXDB)
return nil, nil
}
+24 -3
View File
@@ -38,16 +38,23 @@ func main() {
// functions
fnNameFlag := cli.StringFlag{Name: "name", Usage: "function name"}
fnEnvNameFlag := cli.StringFlag{Name: "env", Usage: "environment name for function"}
fnCodeFlag := cli.StringFlag{Name: "code", Usage: "file containing source code, or - for stdin"}
fnCodeFlag := cli.StringFlag{Name: "code", Usage: "local path or URL for source code"}
fnPackageFlag := cli.StringFlag{Name: "package", Usage: "local path or URL for binary package"}
fnUidFlag := cli.StringFlag{Name: "uid", Usage: "function uid, optional (use latest if unspecified)"}
fnPodFlag := cli.StringFlag{Name: "pod", Usage: "function pod name, optional (use latest if unspecified)"}
fnFollowFlag := cli.BoolFlag{Name: "follow, f", Usage: "specify if the logs should be streamed"}
fnDetailFlag := cli.BoolFlag{Name: "detail, d", Usage: "display detailed information"}
fnLogDBTypeFlag := cli.StringFlag{Name: "dbtype", Usage: "log database type, e.g. influxdb (currently only influxdb is supported)"}
fnSubcommands := []cli.Command{
{Name: "create", Usage: "Create new function (and optionally, an HTTP route to it)", Flags: []cli.Flag{fnNameFlag, fnEnvNameFlag, fnCodeFlag, htUrlFlag, htMethodFlag}, Action: fnCreate},
{Name: "create", Usage: "Create new function (and optionally, an HTTP route to it)", Flags: []cli.Flag{fnNameFlag, fnEnvNameFlag, fnCodeFlag, fnPackageFlag, htUrlFlag, htMethodFlag}, Action: fnCreate},
{Name: "get", Usage: "Get function source code", Flags: []cli.Flag{fnNameFlag, fnUidFlag}, Action: fnGet},
{Name: "edit", Usage: "Edit function source code in $EDITOR", Flags: []cli.Flag{fnNameFlag, fnUidFlag}, Action: fnEdit},
{Name: "getmeta", Usage: "Get function metadata", Flags: []cli.Flag{fnNameFlag, fnUidFlag}, Action: fnGetMeta},
{Name: "update", Usage: "Update function source code", Flags: []cli.Flag{fnNameFlag, fnEnvNameFlag, fnCodeFlag}, Action: fnUpdate},
{Name: "update", Usage: "Update function source code", Flags: []cli.Flag{fnNameFlag, fnEnvNameFlag, fnCodeFlag, fnPackageFlag}, Action: fnUpdate},
{Name: "delete", Usage: "Delete function", Flags: []cli.Flag{fnNameFlag, fnUidFlag}, Action: fnDelete},
{Name: "list", Usage: "List all functions", Flags: []cli.Flag{}, Action: fnList},
{Name: "logs", Usage: "Display funtion logs", Flags: []cli.Flag{fnNameFlag, fnPodFlag, fnFollowFlag, fnDetailFlag, fnLogDBTypeFlag}, Action: fnLogs},
{Name: "pods", Usage: "Display funtion pods", Flags: []cli.Flag{fnNameFlag, fnLogDBTypeFlag}, Action: fnPods},
}
// httptriggers
@@ -62,6 +69,19 @@ func main() {
{Name: "list", Usage: "List HTTP triggers", Flags: []cli.Flag{}, Action: htList},
}
// timetriggers
ttNameFlag := cli.StringFlag{Name: "name", Usage: "Time Trigger name"}
ttCronFlag := cli.StringFlag{Name: "cron", Usage: "Time Trigger cron spec ('0 30 * * *', '@every 5m', '@hourly')"}
ttFnNameFlag := cli.StringFlag{Name: "function", Usage: "Function name"}
ttFnUidFlag := cli.StringFlag{Name: "uid", Usage: "Function UID (optional; uses latest if unspecified)"}
ttSubcommands := []cli.Command{
{Name: "create", Aliases: []string{"add"}, Usage: "Create Time trigger", Flags: []cli.Flag{ttNameFlag, ttFnNameFlag, ttFnUidFlag, ttCronFlag}, Action: ttCreate},
{Name: "get", Usage: "Get Time trigger", Flags: []cli.Flag{}, Action: ttGet},
{Name: "update", Usage: "Update Time trigger", Flags: []cli.Flag{ttNameFlag, ttCronFlag}, Action: ttUpdate},
{Name: "delete", Usage: "Delete Time trigger", Flags: []cli.Flag{ttNameFlag}, Action: ttDelete},
{Name: "list", Usage: "List Time triggers", Flags: []cli.Flag{}, Action: ttList},
}
// environments
envNameFlag := cli.StringFlag{Name: "name", Usage: "Environment name"}
envImageFlag := cli.StringFlag{Name: "image", Usage: "Environment image URL"}
@@ -91,6 +111,7 @@ func main() {
app.Commands = []cli.Command{
{Name: "function", Aliases: []string{"fn"}, Usage: "Create, update and manage functions", Subcommands: fnSubcommands},
{Name: "httptrigger", Aliases: []string{"ht", "route"}, Usage: "Manage HTTP triggers (routes) for functions", Subcommands: htSubcommands},
{Name: "timetrigger", Aliases: []string{"tt", "timer"}, Usage: "Manage Time triggers (timers) for functions", Subcommands: ttSubcommands},
{Name: "environment", Aliases: []string{"env"}, Usage: "Manage environments", Subcommands: envSubcommands},
{Name: "watch", Aliases: []string{"w"}, Usage: "Manage watches", Subcommands: wSubCommands},
+122
View File
@@ -0,0 +1,122 @@
/*
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 main
import (
"fmt"
"os"
"text/tabwriter"
"github.com/satori/go.uuid"
"github.com/urfave/cli"
"github.com/fission/fission"
)
func ttCreate(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
name := c.String("name")
if len(name) == 0 {
name = uuid.NewV4().String()
}
fnName := c.String("function")
if len(fnName) == 0 {
fatal("Need a function name to create a trigger, use --function")
}
fnUid := c.String("uid")
cron := c.String("cron")
if len(cron) == 0 {
fatal("Need a cron spec like '0 30 * * *', '@every 1h30m', '@hourly', use --cron")
}
tt := &fission.TimeTrigger{
Metadata: fission.Metadata{
Name: name,
},
Cron: cron,
Function: fission.Metadata{
Name: fnName,
Uid: fnUid,
},
}
_, err := client.TimeTriggerCreate(tt)
checkErr(err, "create Time trigger")
fmt.Printf("trigger '%v' created\n", name)
return err
}
func ttGet(c *cli.Context) error {
return nil
}
func ttUpdate(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
ttName := c.String("name")
if len(ttName) == 0 {
fatal("Need name of trigger, use --name")
}
tt, err := client.TimeTriggerGet(&fission.Metadata{Name: ttName})
checkErr(err, "get Time trigger")
newCron := c.String("cron")
if len(newCron) != 0 {
tt.Cron = newCron
}
_, err = client.TimeTriggerUpdate(tt)
checkErr(err, "update Time trigger")
fmt.Printf("trigger '%v' updated\n", ttName)
return nil
}
func ttDelete(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
ttName := c.String("name")
if len(ttName) == 0 {
fatal("Need name of trigger to delete, use --name")
}
err := client.TimeTriggerDelete(&fission.Metadata{Name: ttName})
checkErr(err, "delete trigger")
fmt.Printf("trigger '%v' deleted\n", ttName)
return nil
}
func ttList(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
tts, err := client.TimeTriggerList()
checkErr(err, "list Time triggers")
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
fmt.Fprintf(w, "%v\t%v\t%v\t%v\n",
"NAME", "CRON", "FUNCTION_NAME", "FUNCTION_UID")
for _, tt := range tts {
fmt.Fprintf(w, "%v\t%v\t%v\t%v\n",
tt.Metadata.Name, tt.Cron, tt.Function.Name, tt.Function.Uid)
}
w.Flush()
return nil
}
Generated
+15 -14
View File
@@ -1,5 +1,5 @@
hash: fb2f38693c5f2fade3db9434c266743d0f9ea9b5f878b44e79763e248136dcf2
updated: 2016-11-14T01:06:12.764919869-08:00
hash: 51b5eda27974aac228f42002cb0a9f89bf2f79d36a574a3694c142c2f83e4042
updated: 2017-03-24T11:01:00.667996869+08:00
imports:
- name: github.com/blang/semver
version: 60ec3488bfea7cca02b021d106d9911120d25fe9
@@ -69,9 +69,15 @@ imports:
- name: github.com/gorilla/context
version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
- name: github.com/gorilla/handlers
version: ee54c7b44cab12289237fb8631314790076e728b
version: 3a5767ca75ece5f7f1440b1d16975247f8d8b221
- name: github.com/gorilla/mux
version: 0eeaf8392f5b04950925b8a69fe70f110fa7cbfc
version: 392c28fe23e1c45ddba891b0320b3b5df220beea
- name: github.com/influxdata/influxdb
version: b7bb7e8359642b6e071735b50ae41f5eb343fd42
subpackages:
- client/v2
- models
- pkg/escape
- name: github.com/jonboulle/clockwork
version: 2eee05ed794112d45db504eb05aa693efd2b8b09
- name: github.com/juju/ratelimit
@@ -88,10 +94,12 @@ imports:
version: 8a290539e2e8629dbc4e6bad948158f790ec31f4
- name: github.com/PuerkitoBio/urlesc
version: 5bd2802263f21d8788851d5305584c82a5c75d7e
- name: github.com/robfig/cron
version: df38d32658d8788cd446ba74db4bb5375c4b0cb3
- name: github.com/satori/go.uuid
version: 879c5887cd475cd7864858769793b2ceb0d44feb
- name: github.com/Sirupsen/logrus
version: d26492970760ca5d33129d2d799e34be5c4782eb
- name: github.com/sirupsen/logrus
version: ba1b36c82c5e05c4f912a88eab0dcd91a171688f
- name: github.com/spf13/pflag
version: 08b1a584251b5b62f458943640fc8ebd4d50aaa5
- name: github.com/ugorji/go
@@ -99,7 +107,7 @@ imports:
subpackages:
- codec
- name: github.com/urfave/cli
version: a14d7d367bc02b1f57d88de97926727f2d936387
version: 0bdeddeeb0f650497d603c4ad7b20cfe685682f6
- name: golang.org/x/net
version: 6acef71eb69611914f7a30939ea9f6e194c78172
subpackages:
@@ -158,13 +166,6 @@ imports:
- name: k8s.io/client-go
version: 843f7c4f28b1f647f664f883697107d5c02c5acc
subpackages:
- 1.4/kubernetes
- 1.4/pkg/api
- 1.4/pkg/api/v1
- 1.4/pkg/apis/extensions/v1beta1
- 1.4/pkg/labels
- 1.4/pkg/util/intstr
- 1.4/rest
- 1.5/discovery
- 1.5/kubernetes
- 1.5/kubernetes/typed/apps/v1alpha1

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