Compare commits
247
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4fe007838 | ||
|
|
0fd60eab3b | ||
|
|
ae87c0ea37 | ||
|
|
55651d49e0 | ||
|
|
1f0ca47550 | ||
|
|
e619a70661 | ||
|
|
d248bb89ea | ||
|
|
5fe4b276eb | ||
|
|
8f1b27bd72 | ||
|
|
ce4d3ac42d | ||
|
|
3328014ee6 | ||
|
|
b1e66663bd | ||
|
|
c1dd8a9f35 | ||
|
|
d9b98830cf | ||
|
|
a8e7ec534b | ||
|
|
0207d13b23 | ||
|
|
93ef6fac53 | ||
|
|
c43cde8891 | ||
|
|
62aa9d6d73 | ||
|
|
cde2f26a9d | ||
|
|
75d65adcc0 | ||
|
|
2f6cd6d659 | ||
|
|
990d943f21 | ||
|
|
531f69a037 | ||
|
|
3ea9ad2449 | ||
|
|
bc61396071 | ||
|
|
6185e842b5 | ||
|
|
a8c6347ed0 | ||
|
|
e5f5050ea6 | ||
|
|
03e3a63581 | ||
|
|
747a3d2197 | ||
|
|
9bd21d2380 | ||
|
|
a15c867812 | ||
|
|
e1067ab4e5 | ||
|
|
d2b7dc36ab | ||
|
|
1abe961869 | ||
|
|
01b399187e | ||
|
|
b4dd7a83df | ||
|
|
137d8f7285 | ||
|
|
c9f63e8173 | ||
|
|
a71756762b | ||
|
|
a6eb4c6d31 | ||
|
|
a13015e75a | ||
|
|
1665235c14 | ||
|
|
a3f8016442 | ||
|
|
a390c1baa8 | ||
|
|
b5b10a4a27 | ||
|
|
aa77d8686f | ||
|
|
68c006cd33 | ||
|
|
b561f7d948 | ||
|
|
9bdaf3c74e | ||
|
|
e1cb5f6e09 | ||
|
|
a6869ab746 | ||
|
|
365eabb027 | ||
|
|
ad63ba63c8 | ||
|
|
6af605bfe8 | ||
|
|
4d16a4be08 | ||
|
|
6e5b1cf8db | ||
|
|
ca3bf8e83e | ||
|
|
92cd5334ec | ||
|
|
f4b6558e79 | ||
|
|
3b75a86773 | ||
|
|
8cf382c2b3 | ||
|
|
ab6e2e8021 | ||
|
|
eae5150bff | ||
|
|
d4dc166039 | ||
|
|
41baa7b6bf | ||
|
|
6e4115cb5d | ||
|
|
9e5db82cc9 | ||
|
|
6be861afc8 | ||
|
|
430639a1cf | ||
|
|
c8b5bb2972 | ||
|
|
99d6a95bc1 | ||
|
|
14c55912f5 | ||
|
|
858fe9f62b | ||
|
|
25afe7bd78 | ||
|
|
3420a614bb | ||
|
|
d4d917ad04 | ||
|
|
d0ad377d72 | ||
|
|
4046c811b1 | ||
|
|
8127a1f57f | ||
|
|
20cac31635 | ||
|
|
fd07a8a75e | ||
|
|
0d4d2e32a8 | ||
|
|
436057e15b | ||
|
|
33f7647ea7 | ||
|
|
a1d255f038 | ||
|
|
c9ea73786b | ||
|
|
76d4ea7b04 | ||
|
|
2725da6b1b | ||
|
|
c353e7f230 | ||
|
|
7b58ef389d | ||
|
|
9db35b6ff2 | ||
|
|
1cc1fac151 | ||
|
|
39fb45d8da | ||
|
|
1b04f042cd | ||
|
|
7485dca7d8 | ||
|
|
20baa2ee9d | ||
|
|
2fc9fd9603 | ||
|
|
0465ccd352 | ||
|
|
9e01b2f1a7 | ||
|
|
cb08118164 | ||
|
|
043ae0c2b1 | ||
|
|
bba71d65a2 | ||
|
|
f3e1f8b88c | ||
|
|
da15e5cdb7 | ||
|
|
90e7f0212a | ||
|
|
000b74d4ad | ||
|
|
760e021141 | ||
|
|
f5270d97b1 | ||
|
|
8339e120ce | ||
|
|
1d4875ff74 | ||
|
|
e0c7682996 | ||
|
|
4f9b36b042 | ||
|
|
e268ae68e7 | ||
|
|
928d96c080 | ||
|
|
9ffba72c11 | ||
|
|
b1cf2a0182 | ||
|
|
3ed5e772cf | ||
|
|
3f02ccaf34 | ||
|
|
324af61192 | ||
|
|
86a8430c52 | ||
|
|
93ca9cbf3e | ||
|
|
9ea7c80977 | ||
|
|
5460f8ac05 | ||
|
|
6af56753ea | ||
|
|
ac4c11bfb4 | ||
|
|
0fb12af4e5 | ||
|
|
3e386fb41d | ||
|
|
e4f7027a69 | ||
|
|
ebb486d121 | ||
|
|
d5516686c8 | ||
|
|
37a00b6116 | ||
|
|
eccde4fe74 | ||
|
|
595e634617 | ||
|
|
e73242f85c | ||
|
|
5829552c3b | ||
|
|
fa272f20a0 | ||
|
|
cb5cae6501 | ||
|
|
ea2bb9952c | ||
|
|
943d430870 | ||
|
|
c7d2216400 | ||
|
|
63d7c666e0 | ||
|
|
3e24cbcca2 | ||
|
|
08d1e55251 | ||
|
|
a5d96b928c | ||
|
|
f0b31564f0 | ||
|
|
d721df998d | ||
|
|
c5425180e8 | ||
|
|
8704761fd0 | ||
|
|
4d75c3fa4e | ||
|
|
0456df0d63 | ||
|
|
7493759134 | ||
|
|
1fe894ab1c | ||
|
|
cbc4749779 | ||
|
|
1c5954ae61 | ||
|
|
d503834901 | ||
|
|
dcc17d50b8 | ||
|
|
ea4a9faceb | ||
|
|
f84827e384 | ||
|
|
943b15f5a7 | ||
|
|
dddb50c00f | ||
|
|
de30886c9e | ||
|
|
96cc8e402e | ||
|
|
215f4fc30f | ||
|
|
53c49d1485 | ||
|
|
4bc7480acd | ||
|
|
5c6dcca19d | ||
|
|
b7906905c2 | ||
|
|
f1e1da4ceb | ||
|
|
5e3adc0b59 | ||
|
|
0fb4cc1536 | ||
|
|
193cf594be | ||
|
|
7918b89fd2 | ||
|
|
2379cb6f81 | ||
|
|
f29e69c581 | ||
|
|
ddb189bda2 | ||
|
|
188280488c | ||
|
|
9dcd2b711f | ||
|
|
4ae0ab98e8 | ||
|
|
a69834157f | ||
|
|
3a6d5bb070 | ||
|
|
e49af8ca78 | ||
|
|
fa093829bc | ||
|
|
85b0a239c0 | ||
|
|
cfd503af8b | ||
|
|
01249c2d4a | ||
|
|
88f33b9382 | ||
|
|
4a6a088eca | ||
|
|
5f02f6c8ca | ||
|
|
ab96ed2917 | ||
|
|
e8a84dcaa3 | ||
|
|
e627a55988 | ||
|
|
ed426db4a4 | ||
|
|
5e56d99415 | ||
|
|
cd5325090b | ||
|
|
4c14f34969 | ||
|
|
f3aeb28add | ||
|
|
571fa6b0e7 | ||
|
|
ea03e0abc2 | ||
|
|
e92521deab | ||
|
|
dd08d07d59 | ||
|
|
675cc4f360 | ||
|
|
f4be8a73b3 | ||
|
|
69cd025a66 | ||
|
|
c8c0afc68f | ||
|
|
e9c29a8ec8 | ||
|
|
3478d0643b | ||
|
|
c4b7043e70 | ||
|
|
5a007184d4 | ||
|
|
08da045f43 | ||
|
|
3672abbe82 | ||
|
|
36e056c1f4 | ||
|
|
87e2daf455 | ||
|
|
a37b71e063 | ||
|
|
654763cac1 | ||
|
|
460db87bd7 | ||
|
|
3aa0c2e803 | ||
|
|
9e452fd325 | ||
|
|
e94f0c0d6f | ||
|
|
cca8c0dad1 | ||
|
|
6593c15e23 | ||
|
|
18dd175c9f | ||
|
|
ab3f45936a | ||
|
|
84851922f3 | ||
|
|
53e9ba6283 | ||
|
|
640e65f35f | ||
|
|
8a9559be1f | ||
|
|
04efe0bad4 | ||
|
|
a2df49c04f | ||
|
|
c213f18bbb | ||
|
|
c3187a2d23 | ||
|
|
05bc663b3f | ||
|
|
3b5139dca3 | ||
|
|
c733864df5 | ||
|
|
6cdae1ae3a | ||
|
|
54bf4abbcb | ||
|
|
1a19f718d4 | ||
|
|
2a9f229c17 | ||
|
|
2eceb8263d | ||
|
|
d6efca07a2 | ||
|
|
96e14180c4 | ||
|
|
679590883d | ||
|
|
9aab4f0795 | ||
|
|
92846da7c2 | ||
|
|
26aa0998fd | ||
|
|
87a7fa6e26 |
+11
@@ -1 +1,12 @@
|
||||
fission-bundle/fission-bundle
|
||||
# Pycharm IDE
|
||||
.idea
|
||||
environments/php7/vendor/
|
||||
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*~
|
||||
|
||||
vendor/
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
language: go
|
||||
go:
|
||||
- 1.8
|
||||
- tip
|
||||
|
||||
install:
|
||||
- go get github.com/Masterminds/glide
|
||||
- curl -L https://github.com/coreos/etcd/releases/download/v3.0.15/etcd-v3.0.15-linux-amd64.tar.gz -o /tmp/etcd-v3.0.15-linux-amd64.tar.gz && mkdir -p /tmp/test-etcd && tar xzvf /tmp/etcd-v3.0.15-linux-amd64.tar.gz -C /tmp/test-etcd --strip-components=1
|
||||
|
||||
script:
|
||||
- cd ${TRAVIS_BUILD_DIR}
|
||||
- glide install
|
||||
- ./fission-bundle/build.sh
|
||||
- hack/verify-gofmt.sh
|
||||
- /tmp/test-etcd/etcd &
|
||||
- go test -v -i $(go list ./... | grep -v '/vendor/' | grep -v 'examples/go')
|
||||
- go test -v $(go list ./... | grep -v '/vendor/' | grep -v 'examples/go')
|
||||
@@ -0,0 +1,74 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at soamvasani@platform9.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
@@ -0,0 +1,46 @@
|
||||
# Contributing to Fission
|
||||
|
||||
### Thank you!
|
||||
|
||||
Thank you for taking the time and effort to contribute to Fission!
|
||||
|
||||
First, please read the [code of conduct](CODE_OF_CONDUCT.md). By
|
||||
participating, you're expected to uphold this code.
|
||||
|
||||
### Choose something to work on
|
||||
|
||||
The easiest way to start is to look at existing
|
||||
[issues](https://github.com/fission/fission/issues) and see if there's
|
||||
something there that you'd like to work on.
|
||||
|
||||
Also, if you're familiar with a language that we don't support today,
|
||||
adding an environment for a new language is a good task to take on.
|
||||
Take a look at the Python environment as an example.
|
||||
|
||||
If you're interested in working on something that doesn't have an
|
||||
existing issue, feel free to file a new one.
|
||||
|
||||
There's plenty of stuff to do. If you need help choosing something to
|
||||
work on, we'll be happy to suggest something depending on your
|
||||
interests.
|
||||
|
||||
### Talk to people
|
||||
|
||||
At any point, from thinking about contributing to merging your
|
||||
changes, please feel free to reach out to us! We're more than happy
|
||||
to talk.
|
||||
|
||||
If you choose a big chunk of work that involves design changes to
|
||||
Fission, please talk to us while planning the change; that way we can
|
||||
all be on the same page before you invest too much work into it.
|
||||
|
||||
* Drop by the [slack channel](http://slack.fission.io)
|
||||
* Say hi on [twitter](https://twitter.com/fissionio).
|
||||
* Email soamvasani@platform9.com
|
||||
|
||||
### Contribution Howto
|
||||
|
||||
* Fork this github repo
|
||||
* Make a branch in your fork
|
||||
* Commit your changes to that branch
|
||||
* Create a pull request
|
||||
@@ -0,0 +1,55 @@
|
||||
Compiling Fission
|
||||
=================
|
||||
|
||||
[You only need to do this if you're making Fission changes; if you're
|
||||
just deploying Fission, use fission.yaml which points to prebuilt
|
||||
images.]
|
||||
|
||||
You'll need the `go` compiler and tools installed, along with the
|
||||
[glide dependency management
|
||||
tool](https://github.com/Masterminds/glide#install). You'll also need
|
||||
docker for building images.
|
||||
|
||||
The server side is compiled as one binary ("fission-bundle") which
|
||||
contains controller, poolmgr and router; it invokes the right one
|
||||
based on command-line arguments.
|
||||
|
||||
To build fission-bundle: clone this repo to
|
||||
`$GOPATH/src/github.com/fission/fission`, then from the top level
|
||||
directory (if you want to build the image with the docker inside
|
||||
minikube, you'll need to set the proper environment variables with
|
||||
`eval $(minikube docker-env)`):
|
||||
|
||||
```
|
||||
# Get dependencies
|
||||
$ glide install
|
||||
|
||||
# Build fission server and an image
|
||||
$ pushd fission-bundle
|
||||
$ ./build.sh
|
||||
```
|
||||
|
||||
You now need to build the docker image for fission. You can use
|
||||
`push.sh` and push it to a docker hub account. But it's easiest to use
|
||||
minikube and its built-in docker daemon:
|
||||
|
||||
```
|
||||
$ eval $(minikube docker-env)
|
||||
$ docker build -t minikube/fission-bundle .
|
||||
```
|
||||
|
||||
Next, install fission on your kubernetes cluster:
|
||||
|
||||
```
|
||||
# To install, update fission.yaml to point to the image you just made: "minikube/fission-bundle"
|
||||
$ $EDITOR fission.yaml
|
||||
$ kubectl create -f fission.yaml
|
||||
$ kubectl create -f fission-nodeport.yaml
|
||||
```
|
||||
|
||||
And if you're changing the CLI too, you can build it with:
|
||||
|
||||
```
|
||||
# Build Fission CLI
|
||||
$ cd fission && go install
|
||||
```
|
||||
@@ -1,107 +1,144 @@
|
||||
|
||||
A high level view of the internals of Fission.
|
||||
|
||||
How it works
|
||||
============
|
||||
|
||||
Fission is a FaaS -- users create functions (source level), register
|
||||
them with fission using a CLI, and associate functions with triggers.
|
||||
|
||||
Fission wraps those functions into a service, and runs them on
|
||||
Kubernetes on demand.
|
||||
|
||||
Here's an overview of the services that make up fission.
|
||||
|
||||
Components
|
||||
==========
|
||||
|
||||
Language-neutral components:
|
||||
|
||||
* Controller
|
||||
* Container Pool Manager
|
||||
* Container Specializer
|
||||
* Router
|
||||
* controller
|
||||
* poolmgr
|
||||
* router
|
||||
* kubewatcher
|
||||
|
||||
Language-specific components:
|
||||
|
||||
* Language Build Container
|
||||
* Language Run Container
|
||||
* Environment container
|
||||
|
||||
|
||||
Controller
|
||||
----------
|
||||
|
||||
Function and Trigger CRUD APIs. APIs to watch for changes are also
|
||||
included (useful for other components that cache state).
|
||||
|
||||
See api/swagger.json for API details.
|
||||
The controller contains CRUD APIs for functions, http triggers,
|
||||
environments, Kubernetes event watches. This is the component that
|
||||
the client talks to.
|
||||
|
||||
This is the only stateful component. It needs to be configured with a
|
||||
URL to an etcd cluster and a path to a persistent volume. The volume
|
||||
will be used to store the functions' source code.
|
||||
is used to store the functions' source code. Etcd is used as the DB.
|
||||
|
||||
Etcd is used as the DB.
|
||||
[Work to extend to other storage backends is planned, see issue #83.]
|
||||
|
||||
Pool Manager
|
||||
------------
|
||||
|
||||
Container Pool Manager
|
||||
----------------------
|
||||
Poolmgr manages pools of generic containers and function containers.
|
||||
|
||||
Manage pool of generic containers.
|
||||
It has a simple API; both these endpoints are called by the router.
|
||||
|
||||
Probably use K8s RCs. Can we Use labels to move pods from one rc to
|
||||
another? What about jobs?
|
||||
* GetFunctionService takes function metadata and returns the address
|
||||
of a service.
|
||||
|
||||
* TapService lets poolmgr know a service is being used; if it's not
|
||||
called for a few minutes the pod(s) backing the service are killed.
|
||||
|
||||
Container Specializer
|
||||
---------------------
|
||||
Poolmgr watches the controller API and eagerly creates generic pools
|
||||
for environments. It uses Kubernetes deployments to do that. The
|
||||
environment container runs in a pod with the 'fetcher' container.
|
||||
Fetcher is a very simple utility that downloads a URL sent to it and
|
||||
saves it at a configured location.
|
||||
|
||||
Inputs: a running generic language run container, a user function,
|
||||
optionally an http trigger URL.
|
||||
|
||||
Calls Language Run Container and sets up Router to point to it.
|
||||
GetFunctionService "specializes" a pod. The implementation chooses a
|
||||
pod from the pool, relabels it to "orphan" the pod from the
|
||||
deployment, invokes fetcher to copy the function into the pod, and
|
||||
hits the the specialize endpoint on the environment container. This
|
||||
causes the function to be loaded. The pod is now specific to that
|
||||
function.
|
||||
|
||||
This function pod is cached; it's cleaned up if it's unused for a few
|
||||
minutes.
|
||||
|
||||
Router
|
||||
------
|
||||
|
||||
- Cache trigger -> container instance mapping; implement cache miss and expiration.
|
||||
The router forwards HTTP requests to function pods. If there's no
|
||||
running service for a function, it requests one from poolmgr, while
|
||||
holding on to the request; when the function's service is ready it
|
||||
forwards the request.
|
||||
|
||||
- Invoke Specializer, setup up k8s API
|
||||
The router is stateless and can be scaled up if needed, according to
|
||||
load.
|
||||
|
||||
- Forward requests
|
||||
Kubewatcher
|
||||
-----------
|
||||
|
||||
The Router is stateless -- it can be scaled or killed at any time.
|
||||
Kubewatcher watches the Kubernetes API and invokes functions
|
||||
associated with watches, sending the watch event to the function.
|
||||
|
||||
There's a lot of functionality overlap with K8S Ingress Controllers.
|
||||
We should clearly use Ingress and Ingress Controllers in some way.
|
||||
It's not exactly clear at the moment how -- should make a whole new
|
||||
Ingress Controller perhaps based on the contrib/nginx
|
||||
The controller keeps track of user's requested watches and associated
|
||||
functions. Kubewatcher watches the API based on these requests; when
|
||||
a watch event occurs, it serializes the object and calls the function
|
||||
via the router.
|
||||
|
||||
While a few simple retries are done, there isn't yet a reliable
|
||||
message bus between Kubewatcher and the function. Work for this is
|
||||
tracked in issue #64.
|
||||
|
||||
Autoscaler
|
||||
----------
|
||||
Message Queue Trigger
|
||||
---------------------
|
||||
|
||||
This autoscales the language run containers that are backing a
|
||||
trigger.
|
||||
A message queue trigger binds a message queue topic to a function:
|
||||
events from that topic cause the function to be invoked with the
|
||||
message as the body of the request. The trigger may also contain a
|
||||
response topic: if specified, the function's output is sent to this
|
||||
response.
|
||||
|
||||
What metrics this is based on is TBD.
|
||||
Here's a diagram of the components:
|
||||
|
||||
- Number of requests/sec
|
||||
- "Backlog" -- number of outstanding requests not yet started -- how to measure this?
|
||||
- Change in turn-around time?
|
||||

|
||||
|
||||
Environment Container
|
||||
---------------------
|
||||
|
||||
Language Build Container
|
||||
------------------------
|
||||
Environment containers run user-defined functions. Environment
|
||||
containers are language specific. They must contain an HTTP server
|
||||
and a loader for functions.
|
||||
|
||||
* The Language Build Container is a container that is invoked for a
|
||||
build. It takes one user-created function and outputs something
|
||||
that can be run by the corresponding Language Run Container.
|
||||
Poolmgr deploys the environment container into a pod with fetcher
|
||||
(fetcher is a simple utility that can fetch an HTTP url to a file at a
|
||||
configured location). This pod forms a "generic pod", because it can
|
||||
be loaded with any function.
|
||||
|
||||
* The Build Container must implement the Language Build Container
|
||||
interface.
|
||||
When poolmgr needs to create a service for a function, it calls
|
||||
fetcher to fetch the function. Fetcher downloads the function into a
|
||||
volume shared between fetcher and this environment container. Poolmgr
|
||||
then requests the container to load the function.
|
||||
|
||||
Logger
|
||||
------
|
||||
|
||||
Language Run Container
|
||||
----------------------
|
||||
|
||||
* The Language Run Container is the container in which user functions
|
||||
run.
|
||||
|
||||
* The Run Container is started without the user function. It must
|
||||
start as a "Generic Container". It must implement the
|
||||
"specialization interface". In short, it must implement an HTTP
|
||||
server that can receive a piece of code, verify its signature, and
|
||||
map it to an HTTP endpoint. See
|
||||
Documentation/specs/LanguageRunContainerSpec.md for details.
|
||||
|
||||
Logger helps to forward function logs to centralized db service for log
|
||||
persistence. Currently only influxdb is supported to store logs.
|
||||
Following is a diagram describe how log service works:
|
||||
|
||||

|
||||
|
||||
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.
|
||||
@@ -1,71 +0,0 @@
|
||||
Programming Model
|
||||
=================
|
||||
|
||||
This document describes the programming model for fission functions.
|
||||
|
||||
See TERMINOLOGY for definitions of terms, such as _function_,
|
||||
_instance_, and _trigger_.
|
||||
|
||||
|
||||
Idempotency
|
||||
===========
|
||||
|
||||
Fission assumes that functions are idempotent. Functions whose instances
|
||||
die without a result are restarted, upto a certain restart limit.
|
||||
|
||||
|
||||
Time Limits
|
||||
===========
|
||||
|
||||
By default, there is no time limit on fission functions.
|
||||
|
||||
Idle running instances may be killed at any time (usually after the
|
||||
default idle timeout of 10 minutes, but this is configurable).
|
||||
|
||||
|
||||
Mapping
|
||||
=======
|
||||
|
||||
This section specifies the mapping between: (1) HTTP and other
|
||||
triggers, and (2) function parameters, return values, and exceptions.
|
||||
|
||||
In other words, this section describes how a function should be called
|
||||
based on a given trigger, and how the functions behaviour affects the
|
||||
result returned from the trigger.
|
||||
|
||||
The HTTP Trigger
|
||||
----------------
|
||||
|
||||
NodeJS
|
||||
------
|
||||
|
||||
NodeJS functions are called with a context object.
|
||||
|
||||
context.request contains the nodeJS Request object.
|
||||
|
||||
In addition,
|
||||
|
||||
context.queryString contains the parsed querystring
|
||||
|
||||
context.body contains the parsed body
|
||||
|
||||
context.status sets the HTTP response status code. If context.status
|
||||
is an invalid HTTP status, then the HTTP status is set to 500.
|
||||
|
||||
Exceptions result in a HTTP 500 error.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
XXX Should we have a "context.done()"? What are the pros and cons?
|
||||
|
||||
XXX Who handles serialization and deserialization? Should we do json
|
||||
automatically based on content-type and accept headers?
|
||||
|
||||
|
||||
Python
|
||||
------
|
||||
|
||||
Same as node, pretty much.
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
# Fission Roadmap
|
||||
|
||||
## Function features ([area-func](https://github.com/fission/fission/labels/area-func))
|
||||
|
||||
- Secrets, configmaps, env vars
|
||||
- Volumes
|
||||
- Mem requests and limits
|
||||
- Function exec time deadline
|
||||
- Expose a regular K8s Service for a function
|
||||
|
||||
## Fission API ([area-api](https://github.com/fission/fission/labels/area-api))
|
||||
|
||||
- TPR-based controller
|
||||
- API authentication
|
||||
- Aggregated API server
|
||||
|
||||
## Development Workflows ([area-dev](https://github.com/fission/fission/labels/area-dev))
|
||||
|
||||
- Function Versioning
|
||||
- Versioning for a group of functions
|
||||
- Rolling upgrades
|
||||
- for one function
|
||||
- for multiple functions
|
||||
- for functions + other kubernetes deployments
|
||||
- Unit testing
|
||||
|
||||
## API Gateway / Ingress features ([area-ingress](https://github.com/fission/fission/labels/area-ingress))
|
||||
|
||||
- Function authn hooks
|
||||
- K8s Ingress flag
|
||||
- Bundle an ingress controller?
|
||||
|
||||
## Workflows and Function composition ([area-composition](https://github.com/fission/fission/labels/area-composition))
|
||||
|
||||
- Simple Composition - Sync
|
||||
- Simple Composition - Async
|
||||
- Hooks (pre, post, on-error)
|
||||
- Workflows
|
||||
- Testing in the presence of function composition
|
||||
|
||||
## Events ([area-events](https://github.com/fission/fission/labels/area-events))
|
||||
|
||||
- NATS
|
||||
- Kafka
|
||||
- AWS: SNS, SQS
|
||||
- Google PubSub
|
||||
- RabbitMQ
|
||||
- Other event sources
|
||||
|
||||
- Bundle an event queue (NATS streaming, most probably)
|
||||
|
||||
## Operability ([area-ops](https://github.com/fission/fission/labels/area-ops))
|
||||
|
||||
### Fission Install/Upgrade ([area-install](https://github.com/fission/fission/labels/area-install))
|
||||
|
||||
- Helm Installer for Fission
|
||||
- Helm installation for fission functions
|
||||
- CLI installer/upgrader? ("fission upgrade")
|
||||
- Upgrade checker/reminder (like minikube does)
|
||||
- CLI auto-upgrader
|
||||
|
||||
### Function observation ([area-observe](https://github.com/fission/fission/labels/area-observe))
|
||||
|
||||
- Function Logging
|
||||
- Tracing -- Opentracing
|
||||
- Metrics -- Prometheus
|
||||
- Function exception tracking
|
||||
- Logging function load errors
|
||||
- Tracing fission overheads for each function
|
||||
|
||||
## Function Security
|
||||
|
||||
- Function isolation (is authz hook sufficient or do we need something like mutual TLS?)
|
||||
- Function service accounts
|
||||
|
||||
## UX, especially for beginners ([area-ux](https://github.com/fission/fission/labels/area-ux))
|
||||
|
||||
- Fission CLI should include a tutorial
|
||||
- Fission CLI should have a way to drop you into the UI
|
||||
- Eliminate FISSION_URL, just use kube client to find fission url. Also useful to grab credentials.
|
||||
|
||||
## Documentation ([area-doc](https://github.com/fission/fission/labels/area-doc))
|
||||
|
||||
- Installation guide improvements
|
||||
- Troubleshooting guide for common problems
|
||||
- FAQ
|
||||
- Performance overview
|
||||
- Render docs nicely to fission.io
|
||||
|
||||
## Web UI (tracked separately in the fission-ui repo)
|
||||
|
||||
## Performance and Scalability ([area-perf](https://github.com/fission/fission/labels/area-perf))
|
||||
|
||||
- Autoscaling
|
||||
- Cold-start optimization -- optimistically choose from pool, save about ~20msec
|
||||
- Cold-start optimization -- preload funcs in fetcher
|
||||
- Cold-start optimization -- preload libraries in envs (v2) -- mem vs. speed tradeoff
|
||||
|
||||
## Function extensibility ([area-ext](https://github.com/fission/fission/labels/area-ext))
|
||||
|
||||
- Env v2: easy addition of dependencies etc.
|
||||
- Integration with Service Broker
|
||||
|
||||
## Multi-area stuff
|
||||
|
||||
- Execution strategies: cold-start pool vs create-pod-on-cold-start -- one size doesn't fit all, at least with current tech; abstract over execution strategies according to requirements
|
||||
+11
-18
@@ -1,22 +1,15 @@
|
||||
|
||||
_Function_: A _function_ is the smallest unit of a program in Fission.
|
||||
It's a program with an function as an entry point -- it doesn't have
|
||||
to be just one function.
|
||||
_Function_: A fission function is something that's mapped to a
|
||||
_trigger_ and run on demand. Though we call it a "function", this is
|
||||
a bit imprecise, since it's actually a module with an function as an
|
||||
entry point -- it doesn't have to be just one function.
|
||||
|
||||
_Trigger_: Triggers are what cause functions to be called. For
|
||||
example the HTTP trigger causes functions to be called on HTTP
|
||||
requests.
|
||||
example, an HTTP trigger causes functions to be called on HTTP
|
||||
requests. Kubernetes Watch triggers cause functions to be called when
|
||||
a Kubernetes watch changes. Future triggers will include message
|
||||
queues, timers, storage systems, etc.
|
||||
|
||||
_Instance_: Fission creates and runs servers containing these
|
||||
functions. The running server is called an _instance_. You normally
|
||||
don't need to worry about instances: their management is completely
|
||||
automatic and transparent to you. However, certain configuration
|
||||
settings allow you to tweak the behaviour of instances -- for example,
|
||||
you can configure the strategy for destroying unused instances.
|
||||
|
||||
_Generic Instance_: An instance with no user code in it. The process
|
||||
of including the user code to a Generic Instance is _Specialization_,
|
||||
and the resulting instance is a _Specialized Instance_.
|
||||
|
||||
_Specialized Instance_: See _General Instance_.
|
||||
_Environments_: Environments are the language-specific parts of
|
||||
Fission. Environment containers wrap the user's function and present
|
||||
a common interface to the rest of the fission framework.
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
|
||||
Fission is functions-as-a-service for Kubernetes.
|
||||
|
||||
You write some functions, and setup some way for them to be invoked
|
||||
("triggers"). Triggers can be HTTP requests (HTTP Triggers) or timers
|
||||
(Time Triggers).
|
||||
|
||||
Topics
|
||||
======
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
Function Updates
|
||||
----------------
|
||||
@@ -0,0 +1,24 @@
|
||||
# Programming Model
|
||||
|
||||
This document describes the programming model for fission functions.
|
||||
|
||||
## Time Limits
|
||||
|
||||
By default, there is no time limit on fission functions.
|
||||
|
||||
Idle running instances may be killed at any time (usually after the
|
||||
default idle timeout of 10 minutes, but this is configurable).
|
||||
|
||||
|
||||
## HTTP Triggers
|
||||
|
||||
Functions triggered over HTTP receive the HTTP request object in the
|
||||
context. The request's query string, POST body, etc. can be retrieved
|
||||
from this object. The interface is language-specific: see [TODO] for
|
||||
documentation on the context object in each environment.
|
||||
|
||||
|
||||
## Kubernetes Watch Event Triggers
|
||||
|
||||
Kubernetes watches can be used to trigger functions. These functions
|
||||
receive the Kubernetes watch.Event object in JSON-serialized form.
|
||||
@@ -0,0 +1,120 @@
|
||||
# Environment V2 Fission-Environment API
|
||||
|
||||
Fission Environments are the language-specific component of fission.
|
||||
|
||||
They must satisfy the interface in this spec.
|
||||
|
||||
## Meta
|
||||
|
||||
This is version 2.0-alpha of the Fission-Environment API.
|
||||
|
||||
(It's unstable and may change without warning until 2.0-beta.)
|
||||
|
||||
## Overview
|
||||
|
||||
Fission V2 Environments consist of:
|
||||
|
||||
* Metadata
|
||||
* A runtime image
|
||||
* A builder image (optional)
|
||||
* Function Interface Specification
|
||||
* User Documentation
|
||||
* Examples
|
||||
|
||||
### Metadata
|
||||
|
||||
See EnvironmentSpec, Runtime, and Builder in types.go.
|
||||
|
||||
## Runtime Image
|
||||
|
||||
An environment runtime image is a docker container image. It must run
|
||||
a server that has two jobs:
|
||||
|
||||
(a) Loading a "function" from a file path on demand
|
||||
(b) Invoking that function on request
|
||||
|
||||
### Function Loading
|
||||
|
||||
The environment must expose a single HTTP endpoint (at the port and
|
||||
URL specified in the metadata) that loads a function. The function
|
||||
load request is a JSON-serialized `FunctionLoadRequest`.
|
||||
|
||||
The function load request contains a filepath to load the function
|
||||
from. Fission does not define in any way the contents of the path; it
|
||||
is completely environment-dependent. It may be a single file, or a
|
||||
directory (in case of deployment packages).
|
||||
|
||||
The load request may contain an EntryPoint. If it does, the loader
|
||||
must interpret this; usually it's the name of a function in a module
|
||||
or package containing multiple functions. If there is no entrypoint,
|
||||
the environment must use a default; again, the value of thsi default
|
||||
is environment-specific.
|
||||
|
||||
The load request may contain a URL. If it does, requests to that URL
|
||||
should be routed to the function. It defaults to "/".
|
||||
|
||||
### Function Invocation
|
||||
|
||||
Functions are invoked on HTTP request to the server. The port for the
|
||||
request on the runtime container is defined in the Runtime metadata,
|
||||
and the URL for the request is specified in the FunctionLoadRequest.
|
||||
|
||||
The interface of the function is environment specific; the environment
|
||||
must come with a spec for this interface.
|
||||
|
||||
## Builder
|
||||
|
||||
The builder is a container image that contains tools to build a
|
||||
function from source. The source may be a single file or a directory
|
||||
of files.
|
||||
|
||||
The builder container is invoked with the specified command, with the
|
||||
following params:
|
||||
|
||||
1. File path of the source
|
||||
2. File path where the output should go
|
||||
3. Other env or function-specific params passed by the user
|
||||
|
||||
The first two parameters are file paths, and all remaining params are
|
||||
environment-specific.
|
||||
|
||||
The output of the builder should be something that the runtime can
|
||||
load and run -- there should be no intermediate steps that need user
|
||||
intervention.
|
||||
|
||||
### Errors
|
||||
|
||||
## Function Interface Spec
|
||||
|
||||
The function interface spec is a document that specifies the interface
|
||||
of functions and their semantics. It must specify:
|
||||
|
||||
* How functions are invoked (sync, async)
|
||||
* How the request context is provided to the function (URL, headers, request type, request body)
|
||||
* Function logging
|
||||
* Semantics of function errors and exceptions
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
The docs should contain everything necessary to use the environment:
|
||||
|
||||
* How to add it to a fission cluster
|
||||
* How to write and build functions for this environment (link to the interface spec)
|
||||
* How to modify and rebuild the environment itself
|
||||
|
||||
## Examples
|
||||
|
||||
Suggested examples to provide:
|
||||
|
||||
* A simple "Hello world"
|
||||
* A function that demonstrates use of the request context: url params,
|
||||
request headers, request body
|
||||
* A function that does logging
|
||||
* A multi-file function package
|
||||
* A function with dependencies
|
||||
* Functions with shared code
|
||||
|
||||
## Compatibility with v1
|
||||
|
||||
V1 environment images can be used as v2 environment runtime images.
|
||||
@@ -0,0 +1,327 @@
|
||||
# Fission Environments Redesign
|
||||
|
||||
As Fission supports more languages and reaches a wider set of use
|
||||
cases, it's time to ask how well the current Environments design is
|
||||
holding up.
|
||||
|
||||
|
||||
## Environments V1: What we learned
|
||||
|
||||
Environments V1 is very simple idea: an environments is one Docker
|
||||
image with an HTTP server + dynamic loader for that language; it's run
|
||||
in a pod with a language-agnostic sidecar (fetcher) that downloads and
|
||||
saves the function into a volume shared with the language-specific
|
||||
container.
|
||||
|
||||
### Pros:
|
||||
|
||||
* Abstracted away images.
|
||||
|
||||
* Very fast cold start
|
||||
|
||||
* No image registry to manage (neither for the user nor for fission
|
||||
implementation)
|
||||
|
||||
* Relatively small amount of language specific code. (python env is <
|
||||
100 lines)
|
||||
|
||||
### Cons:
|
||||
|
||||
* Doesn't work well for compiled languages
|
||||
|
||||
* Users have to rebuild the image to add dependencies
|
||||
|
||||
* Only one file supported
|
||||
|
||||
* Errors in loading are not surfaced properly. It is especially
|
||||
annoying to wait until runtime to see a syntax error that could have
|
||||
been caught on function upload.
|
||||
|
||||
* Starting a Pod without knowing the functions has its limitations: we
|
||||
can't set CPU/memory limits, we can't mount volumes (persistent
|
||||
volumes, secrets, configmaps). We also can't change the namespace
|
||||
the Pod is in.
|
||||
|
||||
* Not great for a large code base
|
||||
|
||||
* Some people want to operate at the image level but still get the
|
||||
on-demand execution semantics of FaaS. This is a cost-optimization
|
||||
use case.
|
||||
|
||||
|
||||
### Discussion
|
||||
|
||||
Early feedback shows that almost evey user ends up rebuilding images
|
||||
to add some dependecies. Some sort of automated dependecy resolution
|
||||
would be very nice to have and improve the development workflow. In
|
||||
other words, just attach a package.json(nodejs) or
|
||||
requirements.txt(python) with a function, and fission will do the
|
||||
rest. There's also the possiblity of supporting buildpacks (simple
|
||||
zipfiles), a la AWS Lambda.
|
||||
|
||||
Though we can support compiled languages by doing the compilation
|
||||
inside the cold-start, that's not a great solution because: (a)
|
||||
compile errors would be reported at runtime, and (b) because the
|
||||
overhead of compilation doesn't really need to be inside the
|
||||
cold-start latency.
|
||||
|
||||
Non-trivial functions will need multiple files. That also helps for
|
||||
common code across functions. So we need a way for the user to define
|
||||
a function as a collection of code with an entry point.
|
||||
|
||||
Finally, Docker images remain the most flexible way to package an app.
|
||||
Today, users can always rebuild an environment image to include
|
||||
anything they want. But those images must still run a server that
|
||||
implements fission-environment interface (i.e. the specialize
|
||||
endpoint). So perhaps there could be a way for users to say "don't
|
||||
use environments, I've already packaged up my function, here it is".
|
||||
|
||||
|
||||
## Environment V2 Requirements
|
||||
|
||||
Roughly in order of priority:
|
||||
|
||||
0. Retain the simplicity of the simple use cases. First user
|
||||
experience shoud remain trivial -- write a function, map a URL,
|
||||
done.
|
||||
|
||||
1. Support compiled languages. Support error reporting on function
|
||||
upload rather than cold start.
|
||||
|
||||
2. Support functions as a collection of files rather than just one
|
||||
file.
|
||||
|
||||
3. Support automated environment-specific dependecy resolution.
|
||||
|
||||
(#3 may end up having the same solution as #1. You could think of
|
||||
gathering deps as a "compilation" of package.json,
|
||||
requirements.txt, etc.)
|
||||
|
||||
4. Support functions as images.
|
||||
|
||||
|
||||
### User stories
|
||||
|
||||
#### Environment Creation
|
||||
|
||||
V1 Environments were just an image. V2 Environments will be a yaml
|
||||
file with the following properties:
|
||||
|
||||
* Run time image (required)
|
||||
* Version (required)
|
||||
* Builder image (optional)
|
||||
* Build invocation command (required if builder image specified)
|
||||
* File name extension(s) (optional)
|
||||
|
||||
The version will be used to distinguish V2 environments from V1.
|
||||
|
||||
```
|
||||
$ cat golang.yaml
|
||||
type: Environment
|
||||
metadata:
|
||||
name: go
|
||||
spec:
|
||||
runtimeImage: fission/go-runtime
|
||||
builderImage: fission/go-builder
|
||||
buildCommand:
|
||||
- "/build.sh"
|
||||
fileExtentions:
|
||||
- go
|
||||
|
||||
$ fission env create -f golang.yaml
|
||||
```
|
||||
|
||||
#### Function creation for compiled languages
|
||||
|
||||
User writes a function in a compiled language, for example Go.
|
||||
|
||||
```
|
||||
$ fission function create --code blah.go
|
||||
|
||||
<compilation errors>
|
||||
|
||||
<user edits file>
|
||||
$ $EDITOR blah.go
|
||||
<fixes errors>
|
||||
|
||||
$ fission function update --code blah.go
|
||||
<success>
|
||||
|
||||
$ fission route ... # routes work as usual
|
||||
```
|
||||
|
||||
This same user story applies to interpreted languages too, where the
|
||||
"compilation" step can be used to check for syntax errors.
|
||||
|
||||
#### Compiled language, without using fission builds
|
||||
|
||||
User compiles their function locally, resulting in a set of one or
|
||||
more binaries. The user packages these up as a zip file, creating a
|
||||
"deployment package".
|
||||
|
||||
```
|
||||
$ fission function create --deployment-package foo.zip
|
||||
|
||||
$ fission route ... # routes work as usual
|
||||
```
|
||||
|
||||
In this use case, fission is no longer operating at the source
|
||||
level. Builds are left to the user and fission only sees the
|
||||
deployment package package.
|
||||
|
||||
#### Collections of source files
|
||||
|
||||
The user can create a source package -- a set of source files in a
|
||||
zip.
|
||||
|
||||
```
|
||||
$ fission function create --source-package foo.zip
|
||||
|
||||
```
|
||||
|
||||
This workflow works similarly to providing a single source file.
|
||||
|
||||
In addition, fission CLI could support automatic creation of source packages, e.g.
|
||||
|
||||
```
|
||||
$ fission function create --source-files *.js
|
||||
```
|
||||
|
||||
This is purely client-side "syntactic sugar" -- the CLI creates the
|
||||
source package instead of the user having to do it manually. It
|
||||
doesn't change semantics; the source package is still handled as one
|
||||
object.
|
||||
|
||||
#### Handling Dependencies
|
||||
|
||||
The source package of a function can contain dependency specs.
|
||||
|
||||
Fission framework proper does not treat this spec in any special way;
|
||||
it's just another file in the source package. These will be
|
||||
interpreted by the environment builder.
|
||||
|
||||
```
|
||||
$ fission function create --source-files *.js --source-files package.json
|
||||
```
|
||||
|
||||
In this case, the CLI will create a source package containing the JS
|
||||
files and package.json. The NodeJS environment builder will create a
|
||||
deployment package out of these files. The runtime environment will
|
||||
load and run the deployment package.
|
||||
|
||||
#### V1 Compatibility
|
||||
|
||||
V1 Environments will continue to be supported. Existing commands will
|
||||
continue to work. V1 environments won't support newer features like
|
||||
builds, source and deployment packages, etc.
|
||||
|
||||
### Implementation
|
||||
|
||||
#### Environment Type
|
||||
|
||||
The environment type has a set of new properties: version, runtime
|
||||
image, builder image, build command, file extension(s).
|
||||
|
||||
#### Function Type
|
||||
|
||||
The function type has new properties: source package, deployment
|
||||
package. The literal code string continues to be supported, but it
|
||||
will have a specified size limit, say 512KB.
|
||||
|
||||
#### Source and Binary Packages
|
||||
|
||||
A package is just a zip file. It's contents are opaque to fission:
|
||||
the meaning of its contents is defined by the environment. Fission's
|
||||
job is to manage the storage and delivery of the package into build
|
||||
and runtime environments.
|
||||
|
||||
#### Storage Service
|
||||
|
||||
The storage service will have an HTTP API to upload and download
|
||||
files. It can store the packages on a persistent volume or as objects
|
||||
in cloud storage services such as S3.
|
||||
|
||||
Storage service has a garbage collection API endpoint. When invoked,
|
||||
it will remove all packages that are not referenced from any function.
|
||||
|
||||
#### Fetcher
|
||||
|
||||
Fetcher gets some new responsibilities:
|
||||
|
||||
1. It must now also handle zip/unzip of packages
|
||||
|
||||
2. It must know how to upload to the storage service (so it's not
|
||||
exactly "fetcher" any more, but...)
|
||||
|
||||
#### Runtime Environment Interface
|
||||
|
||||
The V2 runtime environment interface is very similar to V1
|
||||
environments. Environments must support a dynamic loader and have an
|
||||
HTTP server that forwards requests to the loaded module.
|
||||
|
||||
The differences:
|
||||
|
||||
* V2 runtimes must support loading a deployment package. Fetcher is
|
||||
responsible for unzipping a deployment package, but interpretation
|
||||
of the contents is up to the environment's code. For example, it
|
||||
may have to include the directory where the deployment package is
|
||||
unzipped in its module load path.
|
||||
|
||||
[TODO any other differences?]
|
||||
|
||||
#### Buildmgr
|
||||
|
||||
A new service that will manage builds. Its design is similar to
|
||||
poolmgr, except it is triggered on creation or update of a function,
|
||||
rather than HTTP requests.
|
||||
|
||||
Buildmgr creates a builder deployment+service for each environment.
|
||||
Pods in this deployment run the environment's build container, and
|
||||
fetcher, with a shared volume between the two containers.
|
||||
|
||||
When a function is created or updated with a source package, buildmgr
|
||||
notices this and triggers a build. First, it calls fetcher to
|
||||
download the source package into a shared volume with the build
|
||||
container. It then invokes the builder by running the build
|
||||
invocation command in the build container. Next, it calls fetcher to
|
||||
package up the output of the builder and store the built package into
|
||||
the the storage service.
|
||||
|
||||
Finally, it updates the function object in the controller API with a
|
||||
reference to the built package.
|
||||
|
||||
[We can collapse this workflow into one request into the builder
|
||||
service, which would make it easier to scale up the builder
|
||||
deployment; if we used multiple requests we'd need some sort of
|
||||
affinity rule, but k8s services only support IP based affinity.]
|
||||
|
||||
#### Poolmgr
|
||||
|
||||
Poolmgr remains relatively unchanged. Instead of contructing URLs for
|
||||
function metadata, it uses the deployment package URL in the function
|
||||
object.
|
||||
|
||||
#### CLI
|
||||
|
||||
Client libraries and CLI have to deal with the new properties in
|
||||
functions and environments.
|
||||
|
||||
The CLI will now talk to both storage service and controller. When a
|
||||
function is created, the user can specify the function in one of 3
|
||||
ways:
|
||||
|
||||
1. One source file, same as v1.
|
||||
|
||||
2. A source package (or a set of source files, which is turned into a
|
||||
source package by the CLI)
|
||||
|
||||
3. A deployment package
|
||||
|
||||
If the file is specified as a source file, fission CLI will use the
|
||||
code literal if it's under the size limit; otherwise it should use the
|
||||
storage service. This will allow users to use fission deployments
|
||||
with no storage service, but with a size limit on functions.
|
||||
|
||||
For the case of a source or deployment package, the CLI first does an
|
||||
upload to the storage service, then creates a function object with a
|
||||
reference to the uploaded package.
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
|
||||
- [Running Fission on your Cluster](#running-fission-on-your-cluster)
|
||||
* [Setup Kubernetes](#setup-kubernetes)
|
||||
+ [Install and start Kubernetes on OSX:](#install-and-start-kubernetes-on-osx)
|
||||
+ [Or, install and start Kubernetes on Linux:](#or-install-and-start-kubernetes-on-linux)
|
||||
* [Verify access to the cluster](#verify-access-to-the-cluster)
|
||||
* [Get and Run Fission: Minikube or Local cluster](#get-and-run-fission-minikube-or-local-cluster)
|
||||
* [Get and Run Fission: GKE or other Cloud](#get-and-run-fission-gke-or-other-cloud)
|
||||
* [Get and Run Fission: OpenShift](#get-and-run-fission-openshift)
|
||||
+ [Using Minishift or Local Cluster](#using-minishift-or-local-cluster)
|
||||
+ [Using other clouds](#using-other-clouds)
|
||||
* [Install the client CLI](#install-the-client-cli)
|
||||
* [Run an example](#run-an-example)
|
||||
* [Enable Persistent Function Logs (Optional)](#enable-persistent-function-logs-optional)
|
||||
* [Use the web based Fission-ui (Optional)](#use-the-web-based-fission-ui-optional)
|
||||
|
||||
## Running Fission on your Cluster
|
||||
|
||||
### Setup Kubernetes
|
||||
|
||||
You can install Kubernetes on your laptop with [minikube](https://github.com/kubernetes/minikube):
|
||||
|
||||
#### Install and start Kubernetes on OSX:
|
||||
```bash
|
||||
$ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin
|
||||
$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.16.0/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
|
||||
$ minikube start
|
||||
```
|
||||
|
||||
#### Or, install and start Kubernetes on Linux:
|
||||
```bash
|
||||
$ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin
|
||||
$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.16.0/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
|
||||
$ minikube start
|
||||
```
|
||||
|
||||
Or, you can use [Google Container Engine's](https://cloud.google.com/container-engine/) free trial to get a 3 node cluster.
|
||||
|
||||
### Verify access to the cluster
|
||||
|
||||
```
|
||||
$ kubectl version
|
||||
```
|
||||
|
||||
### Get and Run Fission: Minikube or Local cluster
|
||||
|
||||
If you're using minikube or no cloud provider, use these commands to
|
||||
set up services with NodePort. This exposes fission on ports 31313
|
||||
and 31314.
|
||||
|
||||
```
|
||||
$ kubectl create -f https://github.com/fission/fission/releases/download/nightly20170621/fission-rbac.yaml
|
||||
$ kubectl create -f https://github.com/fission/fission/releases/download/nightly20170621/fission-nodeport.yaml
|
||||
```
|
||||
|
||||
Set the FISSION_URL and FISSION_ROUTER environment variables.
|
||||
FISSION_URL is used by the fission CLI to find the server.
|
||||
FISSION_URL should be prefixed with a `http://`. (FISSION_ROUTER is
|
||||
only needed for the examples below to work.)
|
||||
|
||||
If you're using minikube, use these commands:
|
||||
|
||||
```
|
||||
$ export FISSION_URL=http://$(minikube ip):31313
|
||||
$ export FISSION_ROUTER=$(minikube ip):31314
|
||||
```
|
||||
|
||||
|
||||
### Get and Run Fission: GKE or other Cloud
|
||||
|
||||
If you're using GKE or any other cloud provider that supports the
|
||||
LoadBalancer service type, use these commands:
|
||||
|
||||
```
|
||||
$ kubectl create -f https://github.com/fission/fission/releases/download/nightly20170621/fission-rbac.yaml
|
||||
$ kubectl create -f https://github.com/fission/fission/releases/download/nightly20170621/fission-cloud.yaml
|
||||
```
|
||||
|
||||
Save the external IP addresses of controller and router services in
|
||||
FISSION_URL and FISSION_ROUTER, respectively. Wait for services to
|
||||
get IP addresses (check this with ```kubectl --namespace fission get
|
||||
svc```). Then:
|
||||
|
||||
```
|
||||
$ export FISSION_URL=http://$(kubectl --namespace fission get svc controller -o=jsonpath='{..ip}')
|
||||
$ export FISSION_ROUTER=$(kubectl --namespace fission get svc router -o=jsonpath='{..ip}')
|
||||
```
|
||||
|
||||
### Get and Run Fission: OpenShift
|
||||
|
||||
If you're using OpenShift, it's possible to run Fission on it! The
|
||||
deployment template needs to be deployed as a user with cluster-admin
|
||||
permissions (like `system:admin`), as it needs to create a
|
||||
`ClusterRole` for deploying function containers from the `fission`
|
||||
namespace/project.
|
||||
|
||||
Identically as with Kubernetes, you need to set the FISSION_URL and FISSION_ROUTER environment variables. If you're using minishift, use these commands:
|
||||
|
||||
```
|
||||
$ export FISSION_URL=http://$(minishift ip):31313¬
|
||||
$ export FISSION_ROUTER=$(minishift ip):31314¬
|
||||
```
|
||||
#### Using Minishift or Local Cluster
|
||||
|
||||
If you're using minishift or no cloud provider, use these commands to set up services with NodePort. This exposes fission on ports 31313 and 31314.
|
||||
|
||||
```
|
||||
$ oc login -u system:admin
|
||||
$ oc create -f https://github.com/fission/fission/releases/download/nightly20170621/fission-openshift.yaml
|
||||
$ oc create -f https://github.com/fission/fission/releases/download/nightly20170621/fission-nodeport.yaml
|
||||
```
|
||||
|
||||
#### Using other clouds
|
||||
|
||||
If you're using any cloud provider that supports the LoadBalancer service type, use these commands:
|
||||
|
||||
```
|
||||
$ oc login -u system:admin
|
||||
$ oc create -f https://github.com/fission/fission/releases/download/nightly20170621/fission-openshift.yaml
|
||||
$ oc create -f https://github.com/fission/fission/releases/download/nightly20170621/fission-cloud.yaml
|
||||
```
|
||||
After these steps, you should be able to run fission client as with kubernetes.
|
||||
|
||||
### Install the client CLI
|
||||
|
||||
#### Mac OS
|
||||
|
||||
Get the CLI binary for Mac:
|
||||
|
||||
```
|
||||
$ curl -Lo fission https://github.com/fission/fission/releases/download/nightly20170621/fission-cli-osx && chmod +x fission && sudo mv fission /usr/local/bin/
|
||||
```
|
||||
|
||||
#### Linux
|
||||
|
||||
```
|
||||
$ curl -Lo fission https://github.com/fission/fission/releases/download/nightly20170621/fission-cli-linux && chmod +x fission && sudo mv fission /usr/local/bin/
|
||||
```
|
||||
|
||||
#### Windows
|
||||
|
||||
For Windows, you can use the linux binary on WSL. Or you can download
|
||||
this windows executable: [fission.exe](https://github.com/fission/fission/releases/download/nightly20170621/fission-cli-windows.exe)
|
||||
|
||||
### Run an example
|
||||
|
||||
Finally, you're ready to use Fission!
|
||||
|
||||
```
|
||||
$ fission env create --name nodejs --image fission/node-env
|
||||
|
||||
$ curl https://raw.githubusercontent.com/fission/fission/master/examples/nodejs/hello.js > hello.js
|
||||
|
||||
$ fission function create --name hello --env nodejs --code hello.js
|
||||
|
||||
$ fission route create --method GET --url /hello --function hello
|
||||
|
||||
$ curl http://$FISSION_ROUTER/hello
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
|
||||
### Enable Persistent Function Logs (Optional)
|
||||
|
||||
Fission uses InfluxDB to store logs and fluentd to forward them from
|
||||
function pods into InfluxDB.
|
||||
|
||||
```
|
||||
$ kubectl create -f fission-logger.yaml
|
||||
```
|
||||
|
||||
That's it for the basic setup. You can now use following command to view function logs:
|
||||
|
||||
```
|
||||
$ fission function logs --name hello
|
||||
```
|
||||
|
||||
You can also list the all the pods that have hosted the function
|
||||
(including ones that aren't alive any more) and view logs for a
|
||||
particular pod:
|
||||
|
||||
```
|
||||
$ fission function pods --name hello
|
||||
|
||||
$ fission function logs --name hello --pod <pod name>
|
||||
```
|
||||
|
||||
### Use the web based Fission-ui (Optional)
|
||||
|
||||
[Fission-ui](https://github.com/fission/fission-ui) is the ui for fission maintained by the community.
|
||||
It allows users to observe and manage fission. It also provides a simple online development environment for serverless functions.
|
||||
|
||||
To setup Fission-ui with fission in k8s is simple:
|
||||
|
||||
```bash
|
||||
# Run this after fission is deployed
|
||||
$ kubectl create -f https://raw.githubusercontent.com/fission/fission-ui/master/docker/fission-ui.yaml
|
||||
```
|
||||
|
||||
Then open `http://node-ip:31319` to use Fission-ui.
|
||||
|
||||
For more infomation, please check out [Fission-ui Readme](https://github.com/fission/fission-ui/blob/master/README.md).
|
||||
|
||||
### Install NATS for message-queue based triggers (Optional)
|
||||
|
||||
Fission supports message queue triggers that allow you to invoke
|
||||
functions based on events in a queue. For now, NATS-Streaming is the
|
||||
only supported message queue.
|
||||
|
||||
You can install NATS Streaming on your Kubernetes cluster with:
|
||||
|
||||
```
|
||||
$ kubectl create -f fission-nats.yaml
|
||||
```
|
||||
|
||||
You can subscribe to a NATS Streaming queue with a command like this:
|
||||
(See `fission mqtrigger --help` for details)
|
||||
|
||||
```
|
||||
$ fission mqtrigger create --name myQueueTrigger --function processEvent --topic "myQueue.request"
|
||||
```
|
||||
@@ -1,79 +1,102 @@
|
||||
Fission
|
||||
=======
|
||||
Fission: Serverless Functions for Kubernetes
|
||||
============================================
|
||||
[](https://travis-ci.org/fission/fission)
|
||||
[](https://goreportcard.com/report/github.com/fission/fission)
|
||||
[](http://slack.fission.io)
|
||||
|
||||
[fission.io](http://fission.io) [@fissionio](http://twitter.com/fissionio)
|
||||
|
||||
Fission is a fast serverless framework for Kubernetes with a focus on
|
||||
developer productivity and high performance.
|
||||
|
||||
It's customizable (with sensible defaults), extensible to any
|
||||
language, and interoperates well with other infrastructure.
|
||||
Fission operates on _just the code_: Docker and Kubernetes are
|
||||
abstracted away under normal operation, though you can use both to
|
||||
extend Fission if you want to.
|
||||
|
||||
See https://fission.io for more.
|
||||
Fission is extensible to any language; the core is written in Go, and
|
||||
language-specific parts are isolated in something called
|
||||
_environments_ (more below). Fission currently supports NodeJS and
|
||||
Python, with more languages coming soon.
|
||||
|
||||
### Performance: 100msec cold start
|
||||
|
||||
Fission maintains a pool of "warm" containers that each contain a
|
||||
small dynamic loader. When a function is first called,
|
||||
i.e. "cold-started", a running container is chosen and the function is
|
||||
loaded. This pool is what makes Fission fast: cold-start latencies
|
||||
are typically about 100msec.
|
||||
|
||||
### Kubernetes is the right place for Serverless
|
||||
|
||||
We're built on Kubernetes because we think any non-trivial app will
|
||||
use a combination of serverless functions and more conventional
|
||||
microservices, and Kubernetes is a great framework to bring these
|
||||
together seamlessly.
|
||||
|
||||
Building on Kubernetes also means that anything you do for operations
|
||||
on your Kubernetes cluster — such as monitoring or log
|
||||
aggregation — also helps with ops on your Fission deployment.
|
||||
|
||||
|
||||
Running Fission
|
||||
===============
|
||||
Fission Concepts
|
||||
----------------
|
||||
|
||||
On your own Kubernetes cluster
|
||||
------------------------------
|
||||
A _function_ is a piece of code that follows the fission function
|
||||
interface.
|
||||
|
||||
### Setup Kubernetes
|
||||
An _environment_ contains the language- and runtime-specific parts of
|
||||
running a function. Fission comes with NodeJS and Python
|
||||
environments; you can also extend environments or create entirely new
|
||||
ones if you want. (An environment is essentially just a container
|
||||
with a webserver and dynamic loader.)
|
||||
|
||||
On your laptop:
|
||||
A _trigger_ is something that maps an event to a function; Fission
|
||||
supports HTTP routes as triggers today, with upcoming support for
|
||||
other types of event triggers, such as timers and Kubernetes events.
|
||||
|
||||
https://github.com/kubernetes/minikube
|
||||
Usage
|
||||
-----
|
||||
|
||||
Or use Google Container Engine.
|
||||
|
||||
### Verify access to the cluster
|
||||
|
||||
$ kubectl version
|
||||
|
||||
### Get and Run Fission
|
||||
|
||||
If you're using GKE, use the fission.yaml unmodified. If you're using
|
||||
minikube, change all instances of LoadBalancer services to NodePort.
|
||||
|
||||
|
||||
$ curl http://fission.io/fission.yaml | kubectl create -f -
|
||||
|
||||
$ kubectl --namespace fission get services
|
||||
|
||||
Save the external IP addresses of controller and router services in
|
||||
FISSION_URL and FISSION_ROUTER, respectively.
|
||||
|
||||
### Install the client CLI
|
||||
|
||||
$ curl http://fission.io/fission > fission
|
||||
|
||||
$ chmod +x fission
|
||||
|
||||
$ sudo mv fission /usr/local/bin/
|
||||
|
||||
### Run an example
|
||||
```bash
|
||||
|
||||
# Add the stock NodeJS env to your Fission deployment
|
||||
$ fission env create --name nodejs --image fission/node-env
|
||||
|
||||
$ echo 'module.exports = function(context, callback) { callback(200, "Hello, world!\n"); }' > hello.js
|
||||
|
||||
# A javascript one-liner that prints "hello world"
|
||||
$ curl https://raw.githubusercontent.com/fission/fission/master/examples/nodejs/hello.js > hello.js
|
||||
|
||||
# Upload your function code to fission
|
||||
$ fission function create --name hello --env nodejs --code hello.js
|
||||
|
||||
|
||||
# Map GET /hello to your new function
|
||||
$ fission route create --method GET --url /hello --function hello
|
||||
|
||||
|
||||
# Run the function. This takes about 100msec the first time.
|
||||
$ curl http://$FISSION_ROUTER/hello
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
See the [examples](examples) directory for more.
|
||||
|
||||
Running Fission on your Cluster
|
||||
===============================
|
||||
|
||||
See the [installation guide](INSTALL.md).
|
||||
|
||||
Compiling Fission
|
||||
=================
|
||||
|
||||
See the [compilation guide](Compiling.md).
|
||||
|
||||
Status
|
||||
======
|
||||
|
||||
Fission is in early alpha. Watch this space for announcements soon!
|
||||
Fission is in early alpha. It's not suitable for production use just
|
||||
yet.
|
||||
|
||||
|
||||
Performance
|
||||
===========
|
||||
|
||||
The alpha release has focussed on cold-start latency performance. For
|
||||
requests that don't have a running instance, i.e. a "cold start",
|
||||
fission has a latency overhead of less than 100 msec for the NodeJS
|
||||
environment.
|
||||
We're looking for early developer feedback -- if you do use Fission,
|
||||
we'd love to hear how it's working for you, what parts in particular
|
||||
you'd like to see improved, and so on. Talk to us on
|
||||
[slack](http://slack.fission.io) or
|
||||
[twitter](https://twitter.com/fissionio).
|
||||
|
||||
|
||||
Vendored
+3
-3
@@ -17,11 +17,11 @@ limitations under the License.
|
||||
package cache
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/platform9/fission"
|
||||
"time"
|
||||
|
||||
"github.com/fission/fission"
|
||||
)
|
||||
|
||||
type requestType int
|
||||
|
||||
Vendored
+9
-7
@@ -16,9 +16,11 @@ limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import "testing"
|
||||
import "log"
|
||||
import "time"
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
@@ -27,11 +29,11 @@ func checkErr(err error) {
|
||||
}
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
c := MakeCache(100 * time.Millisecond)
|
||||
c := MakeCache(100*time.Millisecond, 100*time.Millisecond)
|
||||
|
||||
err := c.Set("a", "b")
|
||||
err, _ := c.Set("a", "b")
|
||||
checkErr(err)
|
||||
err = c.Set("p", "q")
|
||||
err, _ = c.Set("p", "q")
|
||||
checkErr(err)
|
||||
|
||||
val, err := c.Get("a")
|
||||
@@ -53,7 +55,7 @@ func TestCache(t *testing.T) {
|
||||
log.Panicf("found deleted element")
|
||||
}
|
||||
|
||||
err = c.Set("expires", "42")
|
||||
err, _ = c.Set("expires", "42")
|
||||
checkErr(err)
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
_, err = c.Get("expires")
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
```
|
||||
@@ -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!
|
||||
@@ -0,0 +1,16 @@
|
||||
{{/* vim: set filetype=mustache: */}}
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 24 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 24 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
*/}}
|
||||
{{- define "fullname" -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 24 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
Copyright 2016 The Fission Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package fission
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func UrlForFunction(m *Metadata) string {
|
||||
prefix := "/fission-function"
|
||||
if len(m.Uid) > 0 {
|
||||
return fmt.Sprintf("%v/%v/%v", prefix, m.Name, m.Uid)
|
||||
} else {
|
||||
return fmt.Sprintf("%v/%v", prefix, m.Name)
|
||||
}
|
||||
}
|
||||
+71
-7
@@ -21,21 +21,47 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/platform9/fission"
|
||||
"github.com/fission/fission"
|
||||
"github.com/fission/fission/fission/logdb"
|
||||
)
|
||||
|
||||
type API struct {
|
||||
FunctionStore
|
||||
HTTPTriggerStore
|
||||
EnvironmentStore
|
||||
type (
|
||||
API struct {
|
||||
FunctionStore
|
||||
HTTPTriggerStore
|
||||
TimeTriggerStore
|
||||
MessageQueueTriggerStore
|
||||
EnvironmentStore
|
||||
WatchStore
|
||||
}
|
||||
|
||||
logDBConfig struct {
|
||||
httpURL string
|
||||
username string
|
||||
password string
|
||||
}
|
||||
)
|
||||
|
||||
func MakeAPI(rs *ResourceStore) *API {
|
||||
api := &API{
|
||||
FunctionStore: FunctionStore{ResourceStore: *rs},
|
||||
HTTPTriggerStore: HTTPTriggerStore{ResourceStore: *rs},
|
||||
TimeTriggerStore: TimeTriggerStore{ResourceStore: *rs},
|
||||
MessageQueueTriggerStore: MessageQueueTriggerStore{ResourceStore: *rs},
|
||||
EnvironmentStore: EnvironmentStore{ResourceStore: *rs},
|
||||
WatchStore: WatchStore{ResourceStore: *rs},
|
||||
}
|
||||
return api
|
||||
}
|
||||
|
||||
func (api *API) respondWithSuccess(w http.ResponseWriter, resp []byte) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
_, err := w.Write(resp)
|
||||
if err != nil {
|
||||
// this will probably fail too, but try anyway
|
||||
@@ -50,8 +76,26 @@ func (api *API) respondWithError(w http.ResponseWriter, err error) {
|
||||
http.Error(w, msg, code)
|
||||
}
|
||||
|
||||
func (api *API) getLogDBConfig(dbType string) logDBConfig {
|
||||
dbType = strings.ToUpper(dbType)
|
||||
// retrieve db auth config from the env
|
||||
url := os.Getenv(fmt.Sprintf("%s_URL", dbType))
|
||||
if url == "" {
|
||||
// set up default database url
|
||||
url = logdb.INFLUXDB_URL
|
||||
}
|
||||
username := os.Getenv(fmt.Sprintf("%s_USERNAME", dbType))
|
||||
password := os.Getenv(fmt.Sprintf("%s_PASSWORD", dbType))
|
||||
return logDBConfig{
|
||||
httpURL: url,
|
||||
username: username,
|
||||
password: password,
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) HomeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "{message: \"Fission API\", version: \"0.1.0\"}\n")
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
fmt.Fprintf(w, "{\"message\": \"Fission API\", \"version\": \"0.1.0\"}\n")
|
||||
}
|
||||
|
||||
func (api *API) Serve(port int) {
|
||||
@@ -76,6 +120,26 @@ func (api *API) Serve(port int) {
|
||||
r.HandleFunc("/v1/environments/{environment}", api.EnvironmentApiUpdate).Methods("PUT")
|
||||
r.HandleFunc("/v1/environments/{environment}", api.EnvironmentApiDelete).Methods("DELETE")
|
||||
|
||||
r.HandleFunc("/v1/watches", api.WatchApiList).Methods("GET")
|
||||
r.HandleFunc("/v1/watches", api.WatchApiCreate).Methods("POST")
|
||||
r.HandleFunc("/v1/watches/{watch}", api.WatchApiGet).Methods("GET")
|
||||
r.HandleFunc("/v1/watches/{watch}", api.WatchApiUpdate).Methods("PUT")
|
||||
r.HandleFunc("/v1/watches/{watch}", api.WatchApiDelete).Methods("DELETE")
|
||||
|
||||
r.HandleFunc("/v1/triggers/time", api.TimeTriggerApiList).Methods("GET")
|
||||
r.HandleFunc("/v1/triggers/time", api.TimeTriggerApiCreate).Methods("POST")
|
||||
r.HandleFunc("/v1/triggers/time/{timeTrigger}", api.TimeTriggerApiGet).Methods("GET")
|
||||
r.HandleFunc("/v1/triggers/time/{timeTrigger}", api.TimeTriggerApiUpdate).Methods("PUT")
|
||||
r.HandleFunc("/v1/triggers/time/{timeTrigger}", api.TimeTriggerApiDelete).Methods("DELETE")
|
||||
|
||||
r.HandleFunc("/v1/triggers/messagequeue", api.MessageQueueTriggerApiList).Methods("GET")
|
||||
r.HandleFunc("/v1/triggers/messagequeue", api.MessageQueueApiCreate).Methods("POST")
|
||||
r.HandleFunc("/v1/triggers/messagequeue/{mqTrigger}", api.MessageQueueApiGet).Methods("GET")
|
||||
r.HandleFunc("/v1/triggers/messagequeue/{mqTrigger}", api.MessageQueueApiUpdate).Methods("PUT")
|
||||
r.HandleFunc("/v1/triggers/messagequeue/{mqTrigger}", api.MessageQueueApiDelete).Methods("DELETE")
|
||||
|
||||
r.HandleFunc("/proxy/{dbType}", api.FunctionLogsApiPost).Methods("POST")
|
||||
|
||||
address := fmt.Sprintf(":%v", port)
|
||||
|
||||
log.WithFields(log.Fields{"port": port}).Info("Server started")
|
||||
|
||||
+215
-8
@@ -24,18 +24,39 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
etcdClient "github.com/coreos/etcd/client"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/platform9/fission"
|
||||
"github.com/platform9/fission/controller/client"
|
||||
"github.com/fission/fission"
|
||||
"github.com/fission/fission/controller/client"
|
||||
)
|
||||
|
||||
var g struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
func assertNameReuseFails(err error, name string) {
|
||||
assert(err != nil, "recreating "+name+" with same name must fail")
|
||||
fe, ok := err.(fission.Error)
|
||||
assert(ok, "error must be a fission Error")
|
||||
assert(fe.Code == fission.ErrorNameExists, "error must be a name exists error")
|
||||
}
|
||||
|
||||
func assertNotFoundFails(err error, name string) {
|
||||
assert(err != nil, "requesting a non-existent "+name+" must fail")
|
||||
fe, ok := err.(fission.Error)
|
||||
assert(ok, "error must be a fission Error")
|
||||
assert(fe.Code == fission.ErrorNotFound, "error must be a not found error")
|
||||
}
|
||||
|
||||
func assertCronSpecFails(err error) {
|
||||
assert(err != nil, "using an invalid cron spec must fail")
|
||||
fe, ok := err.(fission.Error)
|
||||
assert(ok, "error must be a fission Error")
|
||||
assert(fe.Code == fission.ErrorInvalidArgument, "error must be a invalid argument error")
|
||||
}
|
||||
|
||||
func TestFunctionApi(t *testing.T) {
|
||||
log.SetFormatter(&log.TextFormatter{DisableColors: true})
|
||||
|
||||
@@ -50,12 +71,17 @@ func TestFunctionApi(t *testing.T) {
|
||||
},
|
||||
Code: "code1",
|
||||
}
|
||||
_, err := g.client.FunctionGet(&fission.Metadata{Name: "foo"})
|
||||
assertNotFoundFails(err, "function")
|
||||
|
||||
m, err := g.client.FunctionCreate(testFunc)
|
||||
panicIf(err)
|
||||
uid1 := m.Uid
|
||||
//log.Printf("Created function %v: %v", m.Name, m.Uid)
|
||||
|
||||
_, err = g.client.FunctionCreate(testFunc)
|
||||
assertNameReuseFails(err, "function")
|
||||
|
||||
code, err := g.client.FunctionGetRaw(m)
|
||||
panicIf(err)
|
||||
assert(string(code) == testFunc.Code, "code from FunctionGetRaw must match created function")
|
||||
@@ -101,12 +127,81 @@ func TestFunctionApi(t *testing.T) {
|
||||
assert(len(funcs) == 2,
|
||||
"created two functions, but didn't find them")
|
||||
|
||||
funcs_url := g.client.Url + "/v1/functions"
|
||||
resp, err := http.Get(funcs_url)
|
||||
panicIf(err)
|
||||
defer resp.Body.Close()
|
||||
assert(resp.StatusCode == 200, "http get status code on /v1/functions")
|
||||
|
||||
var found bool = false
|
||||
for _, b := range resp.Header["Content-Type"] {
|
||||
if b == "application/json; charset=utf-8" {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
assert(found, "incorrect response content type")
|
||||
|
||||
err = g.client.FunctionDelete(&fission.Metadata{Name: "foo"})
|
||||
panicIf(err)
|
||||
err = g.client.FunctionDelete(&fission.Metadata{Name: "bar"})
|
||||
panicIf(err)
|
||||
}
|
||||
|
||||
func TestFunctionVersionApi(t *testing.T) {
|
||||
testFunc := &fission.Function{
|
||||
Metadata: fission.Metadata{
|
||||
Name: "foo",
|
||||
Uid: "",
|
||||
},
|
||||
Environment: fission.Metadata{
|
||||
Name: "nodejs",
|
||||
Uid: "xxx",
|
||||
},
|
||||
Code: "code1",
|
||||
}
|
||||
|
||||
testFunc.Code = "code1"
|
||||
m, err := g.client.FunctionCreate(testFunc)
|
||||
panicIf(err)
|
||||
uid1 := m.Uid
|
||||
|
||||
testFunc.Code = "code2"
|
||||
m, err = g.client.FunctionUpdate(testFunc)
|
||||
panicIf(err)
|
||||
uid2 := m.Uid
|
||||
|
||||
err = g.client.FunctionDelete(&fission.Metadata{Name: "foo", Uid: uid1})
|
||||
panicIf(err)
|
||||
|
||||
f, err := g.client.FunctionGet(&fission.Metadata{Name: "foo"})
|
||||
panicIf(err)
|
||||
assert(f.Metadata.Uid == uid2, "deleted version1, but version2 does not exist")
|
||||
|
||||
testFunc.Code = "code3"
|
||||
m, err = g.client.FunctionUpdate(testFunc)
|
||||
panicIf(err)
|
||||
uid3 := m.Uid
|
||||
|
||||
err = g.client.FunctionDelete(&fission.Metadata{Name: "foo", Uid: uid3})
|
||||
panicIf(err)
|
||||
|
||||
f, err = g.client.FunctionGet(&fission.Metadata{Name: "foo"})
|
||||
panicIf(err)
|
||||
assert(f.Metadata.Uid == uid2, "deleted version3, but version2 does not exist")
|
||||
|
||||
testFunc.Code = "code4"
|
||||
m, err = g.client.FunctionUpdate(testFunc)
|
||||
panicIf(err)
|
||||
|
||||
err = g.client.FunctionDelete(&fission.Metadata{Name: "foo"})
|
||||
panicIf(err)
|
||||
|
||||
funcs, err := g.client.FunctionList()
|
||||
panicIf(err)
|
||||
assert(len(funcs) == 0,
|
||||
"created one function with two versions(2 and 4), delete without uid but cannot delete them all")
|
||||
}
|
||||
|
||||
func TestHTTPTriggerApi(t *testing.T) {
|
||||
testTrigger := &fission.HTTPTrigger{
|
||||
Metadata: fission.Metadata{
|
||||
@@ -119,10 +214,16 @@ func TestHTTPTriggerApi(t *testing.T) {
|
||||
Uid: "",
|
||||
},
|
||||
}
|
||||
_, err := g.client.HTTPTriggerGet(&fission.Metadata{Name: "foo"})
|
||||
assertNotFoundFails(err, "trigger")
|
||||
|
||||
m, err := g.client.HTTPTriggerCreate(testTrigger)
|
||||
panicIf(err)
|
||||
defer g.client.HTTPTriggerDelete(m)
|
||||
|
||||
_, err = g.client.HTTPTriggerCreate(testTrigger)
|
||||
assertNameReuseFails(err, "trigger")
|
||||
|
||||
tr, err := g.client.HTTPTriggerGet(m)
|
||||
panicIf(err)
|
||||
testTrigger.Metadata.Uid = m.Uid
|
||||
@@ -140,6 +241,10 @@ func TestHTTPTriggerApi(t *testing.T) {
|
||||
|
||||
testTrigger.Metadata.Name = "yyy"
|
||||
m, err = g.client.HTTPTriggerCreate(testTrigger)
|
||||
assert(err != nil, "duplicate trigger should not be allowed")
|
||||
|
||||
testTrigger.UrlPattern = "/hi2"
|
||||
m, err = g.client.HTTPTriggerCreate(testTrigger)
|
||||
panicIf(err)
|
||||
defer g.client.HTTPTriggerDelete(m)
|
||||
|
||||
@@ -156,10 +261,16 @@ func TestEnvironmentApi(t *testing.T) {
|
||||
},
|
||||
RunContainerImageUrl: "gcr.io/xyz",
|
||||
}
|
||||
_, err := g.client.EnvironmentGet(&fission.Metadata{Name: "foo"})
|
||||
assertNotFoundFails(err, "environment")
|
||||
|
||||
m, err := g.client.EnvironmentCreate(testEnv)
|
||||
panicIf(err)
|
||||
defer g.client.EnvironmentDelete(m)
|
||||
|
||||
_, err = g.client.EnvironmentCreate(testEnv)
|
||||
assertNameReuseFails(err, "environment")
|
||||
|
||||
tr, err := g.client.EnvironmentGet(m)
|
||||
panicIf(err)
|
||||
testEnv.Metadata.Uid = m.Uid
|
||||
@@ -185,22 +296,109 @@ func TestEnvironmentApi(t *testing.T) {
|
||||
assert(len(ts) == 2, "created two envs, but didn't find them")
|
||||
}
|
||||
|
||||
func TestWatchApi(t *testing.T) {
|
||||
testWatch := &fission.Watch{
|
||||
Metadata: fission.Metadata{
|
||||
Name: "xxx",
|
||||
Uid: "yyy",
|
||||
},
|
||||
Namespace: "default",
|
||||
ObjType: "pod",
|
||||
LabelSelector: "",
|
||||
FieldSelector: "",
|
||||
Function: fission.Metadata{
|
||||
Name: "foo",
|
||||
Uid: "",
|
||||
},
|
||||
Target: "",
|
||||
}
|
||||
_, err := g.client.WatchGet(&fission.Metadata{Name: "foo"})
|
||||
assertNotFoundFails(err, "watch")
|
||||
|
||||
m, err := g.client.WatchCreate(testWatch)
|
||||
panicIf(err)
|
||||
defer g.client.WatchDelete(m)
|
||||
|
||||
_, err = g.client.WatchCreate(testWatch)
|
||||
assertNameReuseFails(err, "watch")
|
||||
|
||||
w, err := g.client.WatchGet(m)
|
||||
panicIf(err)
|
||||
testWatch.Metadata.Uid = m.Uid
|
||||
w.Target = ""
|
||||
assert(*testWatch == *w, "watch should match after reading")
|
||||
|
||||
testWatch.Metadata.Name = "yyy"
|
||||
m2, err := g.client.WatchCreate(testWatch)
|
||||
panicIf(err)
|
||||
defer g.client.WatchDelete(m2)
|
||||
|
||||
ws, err := g.client.WatchList()
|
||||
panicIf(err)
|
||||
assert(len(ws) == 2, "created two envs, but didn't find them")
|
||||
}
|
||||
|
||||
func TestTimeTriggerApi(t *testing.T) {
|
||||
testTrigger := &fission.TimeTrigger{
|
||||
Metadata: fission.Metadata{
|
||||
Name: "xxx",
|
||||
Uid: "yyy",
|
||||
},
|
||||
Cron: "0 30 * * * *",
|
||||
Function: fission.Metadata{
|
||||
Name: "foo",
|
||||
Uid: "",
|
||||
},
|
||||
}
|
||||
_, err := g.client.TimeTriggerGet(&fission.Metadata{Name: "foo"})
|
||||
assertNotFoundFails(err, "trigger")
|
||||
|
||||
m, err := g.client.TimeTriggerCreate(testTrigger)
|
||||
panicIf(err)
|
||||
defer g.client.TimeTriggerDelete(m)
|
||||
|
||||
_, err = g.client.TimeTriggerCreate(testTrigger)
|
||||
assertNameReuseFails(err, "trigger")
|
||||
|
||||
tr, err := g.client.TimeTriggerGet(m)
|
||||
panicIf(err)
|
||||
testTrigger.Metadata.Uid = m.Uid
|
||||
assert(*testTrigger == *tr, "trigger should match after reading")
|
||||
|
||||
testTrigger.Cron = "@hourly"
|
||||
m2, err := g.client.TimeTriggerUpdate(testTrigger)
|
||||
panicIf(err)
|
||||
|
||||
m.Uid = m2.Uid
|
||||
tr, err = g.client.TimeTriggerGet(m)
|
||||
panicIf(err)
|
||||
testTrigger.Metadata.Uid = m.Uid
|
||||
assert(*testTrigger == *tr, "trigger should match after reading")
|
||||
|
||||
testTrigger.Metadata.Name = "yyy"
|
||||
testTrigger.Cron = "Not valid cron spec"
|
||||
m, err = g.client.TimeTriggerCreate(testTrigger)
|
||||
assertCronSpecFails(err)
|
||||
|
||||
ts, err := g.client.TimeTriggerList()
|
||||
panicIf(err)
|
||||
assert(len(ts) == 1, "created one trigger, but didn't find it")
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
flag.Parse()
|
||||
|
||||
fileStore, ks, rs := getTestResourceStore()
|
||||
defer os.RemoveAll(fileStore.root)
|
||||
|
||||
api := &API{
|
||||
FunctionStore: FunctionStore{ResourceStore: *rs},
|
||||
HTTPTriggerStore: HTTPTriggerStore{ResourceStore: *rs},
|
||||
EnvironmentStore: EnvironmentStore{ResourceStore: *rs},
|
||||
}
|
||||
api := MakeAPI(rs)
|
||||
g.client = client.MakeClient("http://localhost:8888")
|
||||
|
||||
ks.Delete(context.Background(), "Function", &etcdClient.DeleteOptions{Recursive: true})
|
||||
ks.Delete(context.Background(), "HTTPTrigger", &etcdClient.DeleteOptions{Recursive: true})
|
||||
ks.Delete(context.Background(), "TimeTrigger", &etcdClient.DeleteOptions{Recursive: true})
|
||||
ks.Delete(context.Background(), "Environment", &etcdClient.DeleteOptions{Recursive: true})
|
||||
ks.Delete(context.Background(), "Watch", &etcdClient.DeleteOptions{Recursive: true})
|
||||
|
||||
go api.Serve(8888)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
@@ -208,6 +406,15 @@ func TestMain(m *testing.M) {
|
||||
resp, err := http.Get("http://localhost:8888/")
|
||||
panicIf(err)
|
||||
assert(resp.StatusCode == 200, "http get status code on root")
|
||||
|
||||
var found bool = false
|
||||
for _, b := range resp.Header["Content-Type"] {
|
||||
if b == "application/json; charset=utf-8" {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
assert(found, "incorrect response content type")
|
||||
|
||||
_, err = ioutil.ReadAll(resp.Body)
|
||||
panicIf(err)
|
||||
|
||||
|
||||
+325
-31
@@ -26,9 +26,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/platform9/fission"
|
||||
"github.com/fission/fission"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -80,19 +78,15 @@ func (c *Client) url(relativeUrl string) string {
|
||||
|
||||
func (c *Client) handleResponse(resp *http.Response) ([]byte, error) {
|
||||
if resp.StatusCode != 200 {
|
||||
var errCode int
|
||||
switch resp.StatusCode {
|
||||
case 403:
|
||||
errCode = fission.ErrorNotAuthorized
|
||||
case 404:
|
||||
errCode = fission.ErrorNotFound
|
||||
case 400:
|
||||
errCode = fission.ErrorInvalidArgument
|
||||
default:
|
||||
errCode = fission.ErrorInternal
|
||||
}
|
||||
return nil, fission.MakeError(errCode,
|
||||
fmt.Sprintf("HTTP error %v", resp.StatusCode))
|
||||
return nil, fission.MakeErrorFromHTTP(resp)
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
return body, err
|
||||
}
|
||||
|
||||
func (c *Client) handleCreateResponse(resp *http.Response) ([]byte, error) {
|
||||
if resp.StatusCode != 201 {
|
||||
return nil, fission.MakeErrorFromHTTP(resp)
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
return body, err
|
||||
@@ -114,12 +108,8 @@ func (c *Client) FunctionCreate(f *fission.Function) (*fission.Metadata, error)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
body, err := c.handleCreateResponse(resp)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"name": f.Metadata.Name,
|
||||
"err": err,
|
||||
}).Error("Failed to create function")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -223,6 +213,7 @@ func (c *Client) FunctionList() ([]fission.Function, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
if err != nil {
|
||||
@@ -250,12 +241,8 @@ func (c *Client) HTTPTriggerCreate(t *fission.HTTPTrigger) (*fission.Metadata, e
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
body, err := c.handleCreateResponse(resp)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"name": t.Metadata.Name,
|
||||
"err": err,
|
||||
}).Error("Failed to create http trigger")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -334,6 +321,7 @@ func (c *Client) HTTPTriggerList() ([]fission.HTTPTrigger, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
if err != nil {
|
||||
@@ -361,12 +349,8 @@ func (c *Client) EnvironmentCreate(env *fission.Environment) (*fission.Metadata,
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
body, err := c.handleCreateResponse(resp)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"name": env.Metadata.Name,
|
||||
"err": err,
|
||||
}).Error("Failed to create environment")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -445,6 +429,7 @@ func (c *Client) EnvironmentList() ([]fission.Environment, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
if err != nil {
|
||||
@@ -459,3 +444,312 @@ func (c *Client) EnvironmentList() ([]fission.Environment, error) {
|
||||
|
||||
return envs, nil
|
||||
}
|
||||
|
||||
func (c *Client) WatchCreate(w *fission.Watch) (*fission.Metadata, error) {
|
||||
reqbody, err := json.Marshal(w)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := http.Post(c.url("watches"), "application/json", bytes.NewReader(reqbody))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleCreateResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var m fission.Metadata
|
||||
err = json.Unmarshal(body, &m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
func (c *Client) WatchGet(m *fission.Metadata) (*fission.Watch, error) {
|
||||
relativeUrl := fmt.Sprintf("watches/%v", m.Name)
|
||||
if len(m.Uid) > 0 {
|
||||
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
|
||||
}
|
||||
|
||||
resp, err := http.Get(c.url(relativeUrl))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var w fission.Watch
|
||||
err = json.Unmarshal(body, &w)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &w, nil
|
||||
}
|
||||
|
||||
func (c *Client) WatchUpdate(w *fission.Watch) (*fission.Metadata, error) {
|
||||
return nil, fission.MakeError(fission.ErrorNotImplmented,
|
||||
"watch update not implemented")
|
||||
}
|
||||
|
||||
func (c *Client) WatchDelete(m *fission.Metadata) error {
|
||||
relativeUrl := fmt.Sprintf("watches/%v", m.Name)
|
||||
if len(m.Uid) > 0 {
|
||||
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
|
||||
}
|
||||
err := c.delete(relativeUrl)
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
func (c *Client) WatchList() ([]fission.Watch, error) {
|
||||
resp, err := http.Get(c.url("watches"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
watches := make([]fission.Watch, 0)
|
||||
err = json.Unmarshal(body, &watches)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return watches, err
|
||||
}
|
||||
|
||||
func (c *Client) TimeTriggerCreate(t *fission.TimeTrigger) (*fission.Metadata, error) {
|
||||
reqbody, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := http.Post(c.url("triggers/time"), "application/json", bytes.NewReader(reqbody))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleCreateResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var m fission.Metadata
|
||||
err = json.Unmarshal(body, &m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
func (c *Client) TimeTriggerGet(m *fission.Metadata) (*fission.TimeTrigger, error) {
|
||||
relativeUrl := fmt.Sprintf("triggers/time/%v", m.Name)
|
||||
if len(m.Uid) > 0 {
|
||||
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
|
||||
}
|
||||
|
||||
resp, err := http.Get(c.url(relativeUrl))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var t fission.TimeTrigger
|
||||
err = json.Unmarshal(body, &t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
func (c *Client) TimeTriggerUpdate(t *fission.TimeTrigger) (*fission.Metadata, error) {
|
||||
reqbody, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
relativeUrl := fmt.Sprintf("triggers/time/%v", t.Metadata.Name)
|
||||
|
||||
resp, err := c.put(relativeUrl, "application/json", reqbody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var m fission.Metadata
|
||||
err = json.Unmarshal(body, &m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
func (c *Client) TimeTriggerDelete(m *fission.Metadata) error {
|
||||
relativeUrl := fmt.Sprintf("triggers/time/%v", m.Name)
|
||||
if len(m.Uid) > 0 {
|
||||
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
|
||||
}
|
||||
err := c.delete(relativeUrl)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) TimeTriggerList() ([]fission.TimeTrigger, error) {
|
||||
resp, err := http.Get(c.url("triggers/time"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
triggers := make([]fission.TimeTrigger, 0)
|
||||
err = json.Unmarshal(body, &triggers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return triggers, nil
|
||||
}
|
||||
|
||||
func (c *Client) MessageQueueTriggerCreate(t *fission.MessageQueueTrigger) (*fission.Metadata, error) {
|
||||
reqbody, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := http.Post(c.url("triggers/messagequeue"), "application/json", bytes.NewReader(reqbody))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleCreateResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var m fission.Metadata
|
||||
err = json.Unmarshal(body, &m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
func (c *Client) MessageQueueTriggerGet(m *fission.Metadata) (*fission.MessageQueueTrigger, error) {
|
||||
relativeUrl := fmt.Sprintf("triggers/messagequeue/%v", m.Name)
|
||||
if len(m.Uid) > 0 {
|
||||
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
|
||||
}
|
||||
|
||||
resp, err := http.Get(c.url(relativeUrl))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var t fission.MessageQueueTrigger
|
||||
err = json.Unmarshal(body, &t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
func (c *Client) MessageQueueTriggerUpdate(mqTrigger *fission.MessageQueueTrigger) (*fission.Metadata, error) {
|
||||
reqbody, err := json.Marshal(mqTrigger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
relativeUrl := fmt.Sprintf("triggers/messagequeue/%v", mqTrigger.Metadata.Name)
|
||||
|
||||
resp, err := c.put(relativeUrl, "application/json", reqbody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var m fission.Metadata
|
||||
err = json.Unmarshal(body, &m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
func (c *Client) MessageQueueTriggerDelete(m *fission.Metadata) error {
|
||||
relativeUrl := fmt.Sprintf("triggers/messagequeue/%v", m.Name)
|
||||
if len(m.Uid) > 0 {
|
||||
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
|
||||
}
|
||||
err := c.delete(relativeUrl)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) MessageQueueTriggerList(mqType string) ([]fission.MessageQueueTrigger, error) {
|
||||
relativeUrl := "triggers/messagequeue"
|
||||
if len(mqType) > 0 {
|
||||
relativeUrl += fmt.Sprintf("?mqtype=%v", mqType)
|
||||
}
|
||||
|
||||
resp, err := http.Get(c.url(relativeUrl))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
triggers := make([]fission.MessageQueueTrigger, 0)
|
||||
err = json.Unmarshal(body, &triggers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return triggers, nil
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/platform9/fission"
|
||||
"github.com/fission/fission"
|
||||
)
|
||||
|
||||
func (api *API) EnvironmentApiList(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -47,8 +47,8 @@ func (api *API) EnvironmentApiCreate(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
var env fission.Environment
|
||||
err = json.Unmarshal(body, &env)
|
||||
@@ -71,6 +71,7 @@ func (api *API) EnvironmentApiCreate(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
api.respondWithSuccess(w, resp)
|
||||
}
|
||||
|
||||
@@ -103,6 +104,7 @@ func (api *API) EnvironmentApiUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var env fission.Environment
|
||||
|
||||
@@ -19,7 +19,7 @@ package controller
|
||||
import (
|
||||
"github.com/satori/go.uuid"
|
||||
|
||||
"github.com/platform9/fission"
|
||||
"github.com/fission/fission"
|
||||
)
|
||||
|
||||
type EnvironmentStore struct {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -17,15 +17,17 @@ limitations under the License.
|
||||
package controller
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
|
||||
"encoding/json"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"encoding/base64"
|
||||
"github.com/platform9/fission"
|
||||
"github.com/fission/fission"
|
||||
)
|
||||
|
||||
func (api *API) FunctionApiList(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -48,6 +50,7 @@ func (api *API) FunctionApiCreate(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var f fission.Function
|
||||
@@ -77,6 +80,7 @@ func (api *API) FunctionApiCreate(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
api.respondWithSuccess(w, resp)
|
||||
}
|
||||
|
||||
@@ -115,6 +119,7 @@ func (api *API) FunctionApiUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var f fission.Function
|
||||
@@ -170,3 +175,33 @@ func (api *API) FunctionApiDelete(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
api.respondWithSuccess(w, []byte(""))
|
||||
}
|
||||
|
||||
// FunctionLogsApiPost establishes a proxy server to log database, and redirect
|
||||
// query command send from client to database then proxy back the db response.
|
||||
func (api *API) FunctionLogsApiPost(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
// get dbType from url
|
||||
dbType := vars["dbType"]
|
||||
|
||||
// find correspond db http url
|
||||
dbCnf := api.getLogDBConfig(dbType)
|
||||
|
||||
svcUrl, err := url.Parse(dbCnf.httpURL)
|
||||
if err != nil {
|
||||
log.Printf("Failed to establish proxy server for function logs: %v", err)
|
||||
}
|
||||
// set up proxy server director
|
||||
director := func(req *http.Request) {
|
||||
// only replace url Scheme and Host to remote influxDB
|
||||
// and leave query string intact
|
||||
req.URL.Scheme = svcUrl.Scheme
|
||||
req.URL.Host = svcUrl.Host
|
||||
req.URL.Path = svcUrl.Path
|
||||
// set up http basic auth for database authentication
|
||||
req.SetBasicAuth(dbCnf.username, dbCnf.password)
|
||||
}
|
||||
proxy := &httputil.ReverseProxy{
|
||||
Director: director,
|
||||
}
|
||||
proxy.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ limitations under the License.
|
||||
package controller
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/platform9/fission"
|
||||
"github.com/fission/fission"
|
||||
)
|
||||
|
||||
type FunctionStore struct {
|
||||
@@ -108,7 +108,26 @@ func (fs *FunctionStore) Delete(m fission.Metadata) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fs.ResourceStore.delete(typeName, m.Name)
|
||||
|
||||
bufs, err := fs.ResourceStore.getAll("file/" + m.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(bufs) == 0 {
|
||||
return fs.ResourceStore.delete(typeName, m.Name)
|
||||
}
|
||||
|
||||
fnew, err := fs.Get(&fission.Metadata{Name: m.Name})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
latestUid := bufs[len(bufs)-1] // function always tracks the latest version of code
|
||||
if latestUid == fnew.Uid {
|
||||
return nil
|
||||
}
|
||||
fnew.Uid = latestUid
|
||||
return fs.ResourceStore.update(fnew)
|
||||
}
|
||||
|
||||
func (fs *FunctionStore) List() ([]fission.Function, error) {
|
||||
|
||||
@@ -21,10 +21,10 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/platform9/fission"
|
||||
"github.com/fission/fission"
|
||||
)
|
||||
|
||||
func (api *API) HTTPTriggerApiList(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -47,6 +47,7 @@ func (api *API) HTTPTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var t fission.HTTPTrigger
|
||||
@@ -56,6 +57,20 @@ func (api *API) HTTPTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
triggers, err := api.HTTPTriggerStore.List()
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
for _, url := range triggers {
|
||||
if url.UrlPattern == t.UrlPattern && url.Method == t.Method {
|
||||
err = fission.MakeError(fission.ErrorNameExists,
|
||||
"HTTPTrigger with same URL & method already exists")
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
uid, err := api.HTTPTriggerStore.Create(&t)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
@@ -69,6 +84,7 @@ func (api *API) HTTPTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
api.respondWithSuccess(w, resp)
|
||||
}
|
||||
|
||||
@@ -101,6 +117,7 @@ func (api *API) HTTPTriggerApiUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var t fission.HTTPTrigger
|
||||
|
||||
@@ -19,7 +19,7 @@ package controller
|
||||
import (
|
||||
"github.com/satori/go.uuid"
|
||||
|
||||
"github.com/platform9/fission"
|
||||
"github.com/fission/fission"
|
||||
)
|
||||
|
||||
type HTTPTriggerStore struct {
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
Copyright 2017 The Fission Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/fission/fission"
|
||||
)
|
||||
|
||||
func (api *API) MessageQueueTriggerApiList(w http.ResponseWriter, r *http.Request) {
|
||||
mqType := r.FormValue("mqtype")
|
||||
triggers, err := api.MessageQueueTriggerStore.List(mqType)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
resp, err := json.Marshal(triggers)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
api.respondWithSuccess(w, resp)
|
||||
}
|
||||
|
||||
func (api *API) MessageQueueApiCreate(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var mqTrigger fission.MessageQueueTrigger
|
||||
err = json.Unmarshal(body, &mqTrigger)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// trigger name must not conflict with any other trigger
|
||||
// even they are different message queue type
|
||||
triggers, err := api.MessageQueueTriggerStore.List("")
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
for _, trigger := range triggers {
|
||||
if trigger.Name == mqTrigger.Name {
|
||||
err = fission.MakeError(fission.ErrorNameExists,
|
||||
"Message queue trigger with same name already exists")
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// save trigger info
|
||||
uid, err := api.MessageQueueTriggerStore.Create(&mqTrigger)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
mqTriggerMeta := fission.Metadata{Name: mqTrigger.Metadata.Name, Uid: uid}
|
||||
resp, err := json.Marshal(mqTriggerMeta)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
api.respondWithSuccess(w, resp)
|
||||
}
|
||||
|
||||
func (api *API) MessageQueueApiGet(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
mqTriggerMeta := fission.Metadata{
|
||||
Name: vars["mqTrigger"],
|
||||
Uid: r.FormValue("uid"), // empty if uid is absent
|
||||
}
|
||||
mqTrigger, err := api.MessageQueueTriggerStore.Get(&mqTriggerMeta)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
resp, err := json.Marshal(mqTrigger)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
api.respondWithSuccess(w, resp)
|
||||
}
|
||||
|
||||
func (api *API) MessageQueueApiUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
mqtName := vars["mqTrigger"]
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var mqTrigger fission.MessageQueueTrigger
|
||||
err = json.Unmarshal(body, &mqTrigger)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if mqtName != mqTrigger.Metadata.Name {
|
||||
err = fission.MakeError(fission.ErrorInvalidArgument, "Message queue trigger name doesn't match URL")
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
uid, err := api.MessageQueueTriggerStore.Update(&mqTrigger)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
mqTriggerMeta := fission.Metadata{Name: mqTrigger.Metadata.Name, Uid: uid}
|
||||
resp, err := json.Marshal(mqTriggerMeta)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
api.respondWithSuccess(w, resp)
|
||||
}
|
||||
|
||||
func (api *API) MessageQueueApiDelete(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
mqTriggerMeta := fission.Metadata{
|
||||
Name: vars["mqTrigger"],
|
||||
Uid: r.FormValue("uid"), // empty if uid is absent
|
||||
}
|
||||
|
||||
if len(mqTriggerMeta.Uid) == 0 {
|
||||
log.WithFields(log.Fields{"mqTrigger": mqTriggerMeta.Name}).Info("Deleting all versions")
|
||||
}
|
||||
|
||||
err := api.MessageQueueTriggerStore.Delete(mqTriggerMeta)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
api.respondWithSuccess(w, []byte(""))
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
CopyrigmqTrigger 2017 The Fission Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
mqTriggertp://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/fission/fission"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
type MessageQueueTriggerStore struct {
|
||||
ResourceStore
|
||||
}
|
||||
|
||||
func (mqs *MessageQueueTriggerStore) Create(mqTrigger *fission.MessageQueueTrigger) (string, error) {
|
||||
mqTrigger.Metadata.Uid = uuid.NewV4().String()
|
||||
return mqTrigger.Metadata.Uid, mqs.ResourceStore.create(mqTrigger)
|
||||
}
|
||||
|
||||
func (mqs *MessageQueueTriggerStore) Get(m *fission.Metadata) (*fission.MessageQueueTrigger, error) {
|
||||
var mqTrigger fission.MessageQueueTrigger
|
||||
err := mqs.ResourceStore.read(m.Name, &mqTrigger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mqTrigger, nil
|
||||
}
|
||||
|
||||
func (mqs *MessageQueueTriggerStore) Update(mqTrigger *fission.MessageQueueTrigger) (string, error) {
|
||||
mqTrigger.Metadata.Uid = uuid.NewV4().String()
|
||||
return mqTrigger.Metadata.Uid, mqs.ResourceStore.update(mqTrigger)
|
||||
}
|
||||
|
||||
func (mqs *MessageQueueTriggerStore) Delete(m fission.Metadata) error {
|
||||
typeName, err := getTypeName(fission.MessageQueueTrigger{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return mqs.ResourceStore.delete(typeName, m.Name)
|
||||
}
|
||||
|
||||
func (mqs *MessageQueueTriggerStore) List(mqType string) ([]fission.MessageQueueTrigger, error) {
|
||||
typeName, err := getTypeName(fission.MessageQueueTrigger{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bufs, err := mqs.ResourceStore.getAll(typeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
triggers := make([]fission.MessageQueueTrigger, 0, len(bufs))
|
||||
js := JsonSerializer{}
|
||||
for _, buf := range bufs {
|
||||
var mqTrigger fission.MessageQueueTrigger
|
||||
err = js.deserialize([]byte(buf), &mqTrigger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(mqType) > 0 && mqType != mqTrigger.MessageQueueType {
|
||||
continue
|
||||
}
|
||||
triggers = append(triggers, mqTrigger)
|
||||
}
|
||||
|
||||
return triggers, nil
|
||||
}
|
||||
+48
-15
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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(""))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
Copyright 2016 The Fission Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/fission/fission"
|
||||
)
|
||||
|
||||
func (api *API) WatchApiList(w http.ResponseWriter, r *http.Request) {
|
||||
watches, err := api.WatchStore.List()
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := json.Marshal(watches)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.respondWithSuccess(w, resp)
|
||||
}
|
||||
|
||||
func (api *API) WatchApiCreate(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var watch fission.Watch
|
||||
err = json.Unmarshal(body, &watch)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
watch.Target = fission.UrlForFunction(&watch.Function)
|
||||
|
||||
uid, err := api.WatchStore.Create(&watch)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
m := &fission.Metadata{Name: watch.Metadata.Name, Uid: uid}
|
||||
resp, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
api.respondWithSuccess(w, resp)
|
||||
}
|
||||
|
||||
func (api *API) WatchApiGet(w http.ResponseWriter, r *http.Request) {
|
||||
var m fission.Metadata
|
||||
|
||||
vars := mux.Vars(r)
|
||||
m.Name = vars["watch"]
|
||||
m.Uid = r.FormValue("uid") // empty if uid is absent
|
||||
|
||||
watch, err := api.WatchStore.Get(&m)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := json.Marshal(watch)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.respondWithSuccess(w, resp)
|
||||
}
|
||||
|
||||
func (api *API) WatchApiUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
api.respondWithError(w, fission.MakeError(fission.ErrorNotImplmented,
|
||||
"Not implemented"))
|
||||
}
|
||||
|
||||
func (api *API) WatchApiDelete(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
var m fission.Metadata
|
||||
m.Name = vars["watch"]
|
||||
|
||||
m.Uid = r.FormValue("uid") // empty if uid is absent
|
||||
if len(m.Uid) == 0 {
|
||||
log.WithFields(log.Fields{"watch": m.Name}).Info("Deleting all versions")
|
||||
}
|
||||
|
||||
err := api.WatchStore.Delete(m)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.respondWithSuccess(w, []byte(""))
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
Copyright 2016 The Fission Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/satori/go.uuid"
|
||||
|
||||
"github.com/fission/fission"
|
||||
)
|
||||
|
||||
type WatchStore struct {
|
||||
ResourceStore
|
||||
}
|
||||
|
||||
func (ws *WatchStore) Create(w *fission.Watch) (string, error) {
|
||||
w.Metadata.Uid = uuid.NewV4().String()
|
||||
return w.Metadata.Uid, ws.ResourceStore.create(w)
|
||||
}
|
||||
|
||||
func (ws *WatchStore) Get(m *fission.Metadata) (*fission.Watch, error) {
|
||||
var w fission.Watch
|
||||
err := ws.ResourceStore.read(m.Name, &w)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &w, err
|
||||
}
|
||||
|
||||
func (ws *WatchStore) Update(w *fission.Watch) (string, error) {
|
||||
w.Metadata.Uid = uuid.NewV4().String()
|
||||
return w.Metadata.Uid, ws.ResourceStore.update(w)
|
||||
}
|
||||
|
||||
func (ws *WatchStore) Delete(m fission.Metadata) error {
|
||||
typeName, err := getTypeName(fission.Watch{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ws.ResourceStore.delete(typeName, m.Name)
|
||||
}
|
||||
|
||||
func (ws *WatchStore) List() ([]fission.Watch, error) {
|
||||
typeName, err := getTypeName(fission.Watch{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bufs, err := ws.ResourceStore.getAll(typeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
watches := make([]fission.Watch, 0, len(bufs))
|
||||
js := JsonSerializer{}
|
||||
for _, buf := range bufs {
|
||||
var w fission.Watch
|
||||
err = js.deserialize([]byte(buf), &w)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
watches = append(watches, w)
|
||||
}
|
||||
|
||||
return watches, nil
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
bin/*
|
||||
obj/*
|
||||
out/*
|
||||
.vscode/*
|
||||
project.lock*
|
||||
TODO_dotnet
|
||||
@@ -0,0 +1,9 @@
|
||||
FROM microsoft/dotnet:1.1.0-runtime
|
||||
|
||||
WORKDIR /fission-workdir
|
||||
COPY out .
|
||||
EXPOSE 8888
|
||||
|
||||
ENTRYPOINT ["dotnet"]
|
||||
|
||||
CMD ["fission-dotnet.dll"]
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; } }
|
||||
}
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+21
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
```
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
dotnet restore
|
||||
dotnet publish -c Release -o out
|
||||
docker build -t fission/dotnet-env .
|
||||
Executable
+24
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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).
|
||||
@@ -0,0 +1,5 @@
|
||||
FROM golang:1.8.1
|
||||
|
||||
ENV GOPATH /usr
|
||||
RUN go get github.com/fission/fission
|
||||
|
||||
Executable
+11
@@ -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."
|
||||
@@ -0,0 +1,10 @@
|
||||
package context
|
||||
|
||||
type (
|
||||
Context map[string]interface{}
|
||||
)
|
||||
|
||||
func New() Context {
|
||||
ctx := make(map[string]interface{})
|
||||
return ctx
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
node_modules/
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const process = require('process');
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
@@ -18,6 +19,16 @@ if (!argv.port) {
|
||||
argv.port = 8888;
|
||||
}
|
||||
|
||||
|
||||
// Node resolves module paths according to a file's location. We load
|
||||
// the file from argv.codepath, but tell users to put dependencies in
|
||||
// the server's package.json; this means the function's dependencies
|
||||
// are in /usr/src/app/node_modules. We could be smarter and have the
|
||||
// function deps in the right place in argv.codepath; but for now we
|
||||
// just symlink the function's node_modules to the server's
|
||||
// node_modules.
|
||||
fs.symlinkSync('/usr/src/app/node_modules', `${path.dirname(argv.codepath)}/node_modules`);
|
||||
|
||||
// User function. Starts out undefined.
|
||||
let userFunction;
|
||||
|
||||
@@ -64,11 +75,13 @@ app.all('/', function (req, res) {
|
||||
res.status(500).send("Generic container: no requests supported");
|
||||
return;
|
||||
}
|
||||
|
||||
const context = {
|
||||
request: req,
|
||||
response: res
|
||||
// TODO: context should also have: URL template params, query string
|
||||
};
|
||||
|
||||
function callback(status, body, headers) {
|
||||
if (!status)
|
||||
return;
|
||||
@@ -79,11 +92,32 @@ app.all('/', function (req, res) {
|
||||
}
|
||||
res.status(status).send(body);
|
||||
}
|
||||
try {
|
||||
userFunction(context, callback);
|
||||
} catch(e) {
|
||||
callback(500, "Internal server error")
|
||||
|
||||
//
|
||||
// Customizing the request context
|
||||
//
|
||||
// If you want to modify the context to add anything to it,
|
||||
// you can do that here by adding properties to the context.
|
||||
//
|
||||
|
||||
let functionProm;
|
||||
if (userFunction.length === 1) { // One argument (context)
|
||||
// Make sure their function returns a promise
|
||||
Promise.resolve(userFunction(context)).then(function({ status, body, headers }) {
|
||||
callback(status, body, headers);
|
||||
}).catch(function(err) {
|
||||
console.log(`Function error: ${err}`);
|
||||
callback(500, "Internal server error");
|
||||
});
|
||||
} else { // 2 arguments (context, callback)
|
||||
try {
|
||||
userFunction(context, callback);
|
||||
} catch (err) {
|
||||
console.log(`Function error: ${err}`);
|
||||
callback(500, "Internal server error");
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
app.listen(argv.port);
|
||||
|
||||
@@ -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"
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
@@ -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.
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PHPEnv\\": "src/"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"zendframework/zend-diactoros": "^1.3",
|
||||
"monolog/monolog": "^1.22"
|
||||
}
|
||||
}
|
||||
Generated
+244
@@ -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": []
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
$server = new PHPEnv\Server();
|
||||
$server->run();
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -1,3 +1,14 @@
|
||||
FROM python:3-onbuild
|
||||
CMD [ "python", "./server.py" ]
|
||||
EXPOSE 8888
|
||||
FROM alpine:3.5
|
||||
|
||||
RUN apk update
|
||||
RUN apk add --no-cache python3 python3-dev build-base
|
||||
RUN pip3 install --upgrade pip
|
||||
RUN rm -r /root/.cache
|
||||
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
RUN pip3 install -r requirements.txt
|
||||
|
||||
ENTRYPOINT ["python3"]
|
||||
CMD ["server.py"]
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# Fission: Python Environment
|
||||
|
||||
This is the Python environment for Fission.
|
||||
|
||||
It's a Docker image containing a Python 3.5 runtime, along with a
|
||||
dynamic loader. A few common dependencies are included in the
|
||||
requirements.txt file.
|
||||
|
||||
## Customizing this image
|
||||
|
||||
To add package dependencies, edit requirements.txt to add what you
|
||||
need, and rebuild this image (instructions below).
|
||||
|
||||
You also may want to customize what's available to the function in its
|
||||
request context. You can do this by editing server.py (see the
|
||||
comment in that file about customizing request context).
|
||||
|
||||
## Rebuilding and pushing the image
|
||||
|
||||
You'll need access to a Docker registry to push the image: you can
|
||||
sign up for Docker hub at hub.docker.com, or use registries from
|
||||
gcr.io, quay.io, etc. Let's assume you're using a docker hub account
|
||||
called USER. Build and push the image to the the registry:
|
||||
|
||||
```
|
||||
docker build -t USER/python-env . && docker push USER/python-env
|
||||
```
|
||||
|
||||
## Using the image in fission
|
||||
|
||||
You can add this customized image to fission with "fission env
|
||||
create":
|
||||
|
||||
```
|
||||
fission env create --name python --image USER/python-env
|
||||
```
|
||||
|
||||
Or, if you already have an environment, you can update its image:
|
||||
|
||||
```
|
||||
fission env update --name python --image USER/python-env
|
||||
```
|
||||
|
||||
After this, fission functions that have the env parameter set to the
|
||||
same environment name as this command will use this environment.
|
||||
@@ -0,0 +1,6 @@
|
||||
Flask===0.11.1
|
||||
httplib2
|
||||
python-dateutil
|
||||
requests==2.7.0
|
||||
redis
|
||||
hiredis
|
||||
|
||||
@@ -1,42 +1,55 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import imp
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
#
|
||||
# Load the file.
|
||||
#
|
||||
from flask import Flask, request, abort, g
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
codepath = '/userfunc/user'
|
||||
|
||||
userfunc = None
|
||||
|
||||
class testHTTPServer_RequestHandler(BaseHTTPRequestHandler):
|
||||
def do_POST(self):
|
||||
global userfunc
|
||||
userfunc = (imp.load_source('user', codepath)).main
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(bytes("ok\n", "utf8"))
|
||||
return
|
||||
@app.route('/specialize', methods=['POST'])
|
||||
def load():
|
||||
global userfunc
|
||||
userfunc = (imp.load_source('user', codepath)).main
|
||||
return ""
|
||||
|
||||
# GET
|
||||
def do_GET(self):
|
||||
global userfunc
|
||||
try:
|
||||
print("GET request")
|
||||
message = userfunc(None)
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(bytes(message, "utf8"))
|
||||
return
|
||||
except:
|
||||
self.send_response(500)
|
||||
self.end_headers()
|
||||
@app.route('/', methods=['GET', 'POST', 'PUT', 'HEAD', 'OPTIONS', 'DELETE'])
|
||||
def f():
|
||||
if userfunc == None:
|
||||
print("Generic container: no requests supported")
|
||||
abort(500)
|
||||
#
|
||||
# Customizing the request context
|
||||
#
|
||||
# If you want to pass something to the function, you can add it to 'g':
|
||||
# g.myKey = myValue
|
||||
# And the user func can then access that (after doing a "from flask import g").
|
||||
#
|
||||
return userfunc()
|
||||
|
||||
def run():
|
||||
print('starting server...')
|
||||
server_address = ('0.0.0.0', 8888)
|
||||
httpd = HTTPServer(server_address, testHTTPServer_RequestHandler)
|
||||
httpd.serve_forever()
|
||||
#
|
||||
# Logging setup. TODO: Loglevel hard-coded for now. We could allow
|
||||
# functions/routes to override this somehow; or we could create
|
||||
# separate dev vs. prod environments.
|
||||
#
|
||||
def setup_logger(loglevel):
|
||||
global app
|
||||
root = logging.getLogger()
|
||||
root.setLevel(loglevel)
|
||||
ch = logging.StreamHandler(sys.stdout)
|
||||
ch.setLevel(loglevel)
|
||||
ch.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
|
||||
app.logger.addHandler(ch)
|
||||
|
||||
|
||||
run()
|
||||
#
|
||||
# TODO: this starts the built-in server, which isn't the most
|
||||
# efficient. We should use something better.
|
||||
#
|
||||
setup_logger(logging.DEBUG)
|
||||
app.logger.info("Starting server")
|
||||
app.run(host='0.0.0.0', port='8888')
|
||||
|
||||
@@ -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"]
|
||||
@@ -0,0 +1,4 @@
|
||||
# frozen_string_literal: true
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "rack"
|
||||
@@ -0,0 +1,13 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
rack (2.0.3)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
rack
|
||||
|
||||
BUNDLED WITH
|
||||
1.15.1
|
||||
@@ -0,0 +1,55 @@
|
||||
# Fission: Ruby Environment
|
||||
|
||||
This is the Ruby environment for Fission.
|
||||
|
||||
It's a Docker image containing a Ruby 2.4.1 runtime. The image uses
|
||||
Rack with WEBrick to host the internal web server.
|
||||
|
||||
The environment works via convention where you create a Ruby method
|
||||
called `handler` with a single optional argument, a `Fission::Context`
|
||||
object.
|
||||
|
||||
The `Fission::Context` object gives access to the Rack env, and a
|
||||
request object. Please see `fission/context.rb` for the public api.
|
||||
|
||||
The `Fission::Request` object is a subclass of `Rack::Request` and
|
||||
provides access to parameters and headers. See `fission/request.rb`
|
||||
for the public api.
|
||||
|
||||
## Customizing this image
|
||||
|
||||
To add package dependencies, edit Gemfile to add what you
|
||||
need, and rebuild this image (instructions below).
|
||||
|
||||
## Rebuilding and pushing the image
|
||||
|
||||
You'll need access to a Docker registry to push the image: you can
|
||||
sign up for Docker hub at hub.docker.com, or use registries from
|
||||
gcr.io, quay.io, etc. Let's assume you're using a docker hub account
|
||||
called USER. Build and push the image to the the registry:
|
||||
|
||||
```
|
||||
docker build -t USER/ruby-env . && docker push USER/ruby-env
|
||||
```
|
||||
|
||||
## Using the image in fission
|
||||
|
||||
You can add this customized image to fission with "fission env
|
||||
create":
|
||||
|
||||
```
|
||||
fission env create --name ruby --image USER/ruby-env
|
||||
```
|
||||
|
||||
Or, if you already have an environment, you can update its image:
|
||||
|
||||
```
|
||||
fission env update --name ruby --image USER/ruby-env
|
||||
```
|
||||
|
||||
After this, fission functions that have the env parameter set to the
|
||||
same environment name as this command will use this environment.
|
||||
|
||||
## Creating functions to use this image
|
||||
|
||||
See the [examples README](examples/ruby/README.md).
|
||||
@@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'request'
|
||||
|
||||
module Fission
|
||||
class Context
|
||||
attr_reader :env
|
||||
|
||||
def initialize(env)
|
||||
@env = env
|
||||
end
|
||||
|
||||
def request
|
||||
@request ||= Request.new(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'context'
|
||||
|
||||
module Fission
|
||||
module Handler
|
||||
def self.call(env)
|
||||
response = if method(:handler).arity > 0
|
||||
handler(Context.new(env))
|
||||
else
|
||||
handler
|
||||
end
|
||||
|
||||
response.is_a?(Array) ? response : Rack::Response.new([response]).finish
|
||||
rescue
|
||||
Rack::Response.new(['500 Internal Server Error'], 500, {}).finish
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Fission
|
||||
HEADER_PREFIX = 'HTTP_'
|
||||
PARAM_HEADER_PREFIX = 'HTTP_X_FISSION_PARAMS_'
|
||||
PARAMETERS_KEY = 'fission.request.parameters'
|
||||
|
||||
class Request < Rack::Request
|
||||
def headers
|
||||
Hash[
|
||||
*env.select { |k,v| k.start_with?(HEADER_PREFIX) }
|
||||
.map { |k,v| [k.sub(/\A#{HEADER_PREFIX}/, '').split('_').map(&:capitalize).join('-'), v] }
|
||||
.sort
|
||||
.flatten
|
||||
]
|
||||
end
|
||||
|
||||
def params
|
||||
env[PARAMETERS_KEY] ||= super.merge(path_parameters)
|
||||
end
|
||||
|
||||
def path_parameters
|
||||
Hash[
|
||||
*env.select { |k,v| k.start_with?(PARAM_HEADER_PREFIX) }
|
||||
.map { |k,v| [k.sub(/\A#{PARAM_HEADER_PREFIX}/, '').downcase, v] }
|
||||
.sort
|
||||
.flatten
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Fission
|
||||
CODE_PATH = '/userfunc/user'
|
||||
|
||||
module Specializer
|
||||
def self.call(env)
|
||||
load CODE_PATH
|
||||
Rack::Response.new([], 201).finish
|
||||
rescue
|
||||
Rack::Response.new(['500 Internal Server Error'], 500, {}).finish
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rack'
|
||||
|
||||
require_relative 'fission/specializer'
|
||||
require_relative 'fission/handler'
|
||||
|
||||
app = Rack::Builder.new do
|
||||
use Rack::CommonLogger, $stderr
|
||||
|
||||
map "/specialize" do
|
||||
run Fission::Specializer
|
||||
end
|
||||
|
||||
map "/" do
|
||||
run Fission::Handler
|
||||
end
|
||||
end
|
||||
|
||||
Rack::Handler::WEBrick.run app, Host: '0.0.0.0', Port: 8888
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using Fission.DotNetCore.Api;
|
||||
|
||||
public class FissionFunction
|
||||
{
|
||||
public string Execute(FissionContext context)
|
||||
{
|
||||
return "Hello World!";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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!
|
||||
```
|
||||
@@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
msg := "Hello, World!"
|
||||
w.Write([]byte(msg))
|
||||
}
|
||||
@@ -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
|
||||
```
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
module.exports = function(context, callback) {
|
||||
callback(200, "Hello, world!\n");
|
||||
}
|
||||
@@ -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"
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
echo "Hello from PHP";
|
||||
$logger->warning("Hello logger");
|
||||
@@ -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");
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
use \Psr\Http\Message\ResponseInterface;
|
||||
use \Psr\Http\Message\ServerRequestInterface;
|
||||
use \Psr\Log\LoggerInterface;
|
||||
|
||||
function sendError($response,$message){
|
||||
$response = $response->withStatus(500);
|
||||
$response->getBody()->write($message);
|
||||
return $response;
|
||||
}
|
||||
|
||||
function handler($context){
|
||||
/** @var ResponseInterface $response */
|
||||
$response = $context["response"];
|
||||
/** @var ServerRequestInterface $request */
|
||||
$request = $context["request"];
|
||||
/** @var LoggerInterface $logger */
|
||||
$logger = $context["logger"];
|
||||
|
||||
$logger->debug("Request : ",$request->getParsedBody());
|
||||
if($request->getMethod() != "POST")
|
||||
return sendError($response,"You must use POST method");
|
||||
|
||||
$body = $request->getParsedBody();
|
||||
if(!isset($body["currency"]))
|
||||
return sendError($response,"'currency' is not present in the POST request");
|
||||
|
||||
$allowedCurrency = ["ltc","btc"];
|
||||
if(!in_array($body["currency"],$allowedCurrency))
|
||||
return sendError($response,"'currency' is non allowed. Use one of them : ".implode(",",$allowedCurrency));
|
||||
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, array(
|
||||
CURLOPT_RETURNTRANSFER => 1,
|
||||
CURLOPT_URL => 'https://api.cryptonator.com/api/ticker/'.$body["currency"].'-usd'
|
||||
));
|
||||
$result = curl_exec($curl);
|
||||
curl_close($curl);
|
||||
|
||||
$result = json_decode($result,true);
|
||||
if($result){
|
||||
$logger->debug("Response API",$result);
|
||||
$response->getBody()->write(json_encode(array("text"=>sprintf("%s-USB = %02f",$body["currency"],$result["ticker"]["price"]))));
|
||||
}else
|
||||
return sendError($response,"Cryptonator API not available");
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user