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.
|
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
|
Components
|
||||||
==========
|
==========
|
||||||
|
|
||||||
Language-neutral components:
|
Language-neutral components:
|
||||||
|
|
||||||
* Controller
|
* controller
|
||||||
* Container Pool Manager
|
* poolmgr
|
||||||
* Container Specializer
|
* router
|
||||||
* Router
|
* kubewatcher
|
||||||
|
|
||||||
Language-specific components:
|
Language-specific components:
|
||||||
|
|
||||||
* Language Build Container
|
* Environment container
|
||||||
* Language Run Container
|
|
||||||
|
|
||||||
|
|
||||||
Controller
|
Controller
|
||||||
----------
|
----------
|
||||||
|
|
||||||
Function and Trigger CRUD APIs. APIs to watch for changes are also
|
The controller contains CRUD APIs for functions, http triggers,
|
||||||
included (useful for other components that cache state).
|
environments, Kubernetes event watches. This is the component that
|
||||||
|
the client talks to.
|
||||||
See api/swagger.json for API details.
|
|
||||||
|
|
||||||
This is the only stateful component. It needs to be configured with a
|
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
|
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
|
* GetFunctionService takes function metadata and returns the address
|
||||||
another? What about jobs?
|
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,
|
GetFunctionService "specializes" a pod. The implementation chooses a
|
||||||
optionally an http trigger URL.
|
pod from the pool, relabels it to "orphan" the pod from the
|
||||||
|
deployment, invokes fetcher to copy the function into the pod, and
|
||||||
Calls Language Run Container and sets up Router to point to it.
|
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
|
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.
|
The controller keeps track of user's requested watches and associated
|
||||||
We should clearly use Ingress and Ingress Controllers in some way.
|
functions. Kubewatcher watches the API based on these requests; when
|
||||||
It's not exactly clear at the moment how -- should make a whole new
|
a watch event occurs, it serializes the object and calls the function
|
||||||
Ingress Controller perhaps based on the contrib/nginx
|
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
|
A message queue trigger binds a message queue topic to a function:
|
||||||
trigger.
|
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
|
Poolmgr deploys the environment container into a pod with fetcher
|
||||||
build. It takes one user-created function and outputs something
|
(fetcher is a simple utility that can fetch an HTTP url to a file at a
|
||||||
that can be run by the corresponding Language Run Container.
|
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
|
When poolmgr needs to create a service for a function, it calls
|
||||||
interface.
|
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
|
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:
|
||||||
* 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.
|
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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 fission function is something that's mapped to a
|
||||||
_Function_: A _function_ is the smallest unit of a program in Fission.
|
_trigger_ and run on demand. Though we call it a "function", this is
|
||||||
It's a program with an function as an entry point -- it doesn't have
|
a bit imprecise, since it's actually a module with an function as an
|
||||||
to be just one function.
|
entry point -- it doesn't have to be just one function.
|
||||||
|
|
||||||
_Trigger_: Triggers are what cause functions to be called. For
|
_Trigger_: Triggers are what cause functions to be called. For
|
||||||
example the HTTP trigger causes functions to be called on HTTP
|
example, an HTTP trigger causes functions to be called on HTTP
|
||||||
requests.
|
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
|
_Environments_: Environments are the language-specific parts of
|
||||||
functions. The running server is called an _instance_. You normally
|
Fission. Environment containers wrap the user's function and present
|
||||||
don't need to worry about instances: their management is completely
|
a common interface to the rest of the fission framework.
|
||||||
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_.
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
Fission is a fast serverless framework for Kubernetes with a focus on
|
||||||
developer productivity and high performance.
|
developer productivity and high performance.
|
||||||
|
|
||||||
It's customizable (with sensible defaults), extensible to any
|
Fission operates on _just the code_: Docker and Kubernetes are
|
||||||
language, and interoperates well with other infrastructure.
|
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.
|
```bash
|
||||||
|
|
||||||
### 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
|
|
||||||
|
|
||||||
|
# Add the stock NodeJS env to your Fission deployment
|
||||||
$ fission env create --name nodejs --image fission/node-env
|
$ 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
|
$ 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
|
$ fission route create --method GET --url /hello --function hello
|
||||||
|
|
||||||
|
# Run the function. This takes about 100msec the first time.
|
||||||
$ curl http://$FISSION_ROUTER/hello
|
$ curl http://$FISSION_ROUTER/hello
|
||||||
Hello, world!
|
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
|
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.
|
||||||
|
|
||||||
|
We're looking for early developer feedback -- if you do use Fission,
|
||||||
Performance
|
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
|
||||||
The alpha release has focussed on cold-start latency performance. For
|
[twitter](https://twitter.com/fissionio).
|
||||||
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.
|
|
||||||
|
|
||||||
|
|||||||
Vendored
+3
-3
@@ -17,11 +17,11 @@ limitations under the License.
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/platform9/fission"
|
"time"
|
||||||
|
|
||||||
|
"github.com/fission/fission"
|
||||||
)
|
)
|
||||||
|
|
||||||
type requestType int
|
type requestType int
|
||||||
|
|||||||
Vendored
+9
-7
@@ -16,9 +16,11 @@ limitations under the License.
|
|||||||
|
|
||||||
package cache
|
package cache
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
import "log"
|
"log"
|
||||||
import "time"
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
func checkErr(err error) {
|
func checkErr(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -27,11 +29,11 @@ func checkErr(err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCache(t *testing.T) {
|
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)
|
checkErr(err)
|
||||||
err = c.Set("p", "q")
|
err, _ = c.Set("p", "q")
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
||||||
val, err := c.Get("a")
|
val, err := c.Get("a")
|
||||||
@@ -53,7 +55,7 @@ func TestCache(t *testing.T) {
|
|||||||
log.Panicf("found deleted element")
|
log.Panicf("found deleted element")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Set("expires", "42")
|
err, _ = c.Set("expires", "42")
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
time.Sleep(150 * time.Millisecond)
|
time.Sleep(150 * time.Millisecond)
|
||||||
_, err = c.Get("expires")
|
_, 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"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
"github.com/gorilla/mux"
|
"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 {
|
type (
|
||||||
FunctionStore
|
API struct {
|
||||||
HTTPTriggerStore
|
FunctionStore
|
||||||
EnvironmentStore
|
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) {
|
func (api *API) respondWithSuccess(w http.ResponseWriter, resp []byte) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
_, err := w.Write(resp)
|
_, err := w.Write(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// this will probably fail too, but try anyway
|
// 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)
|
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) {
|
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) {
|
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.EnvironmentApiUpdate).Methods("PUT")
|
||||||
r.HandleFunc("/v1/environments/{environment}", api.EnvironmentApiDelete).Methods("DELETE")
|
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)
|
address := fmt.Sprintf(":%v", port)
|
||||||
|
|
||||||
log.WithFields(log.Fields{"port": port}).Info("Server started")
|
log.WithFields(log.Fields{"port": port}).Info("Server started")
|
||||||
|
|||||||
+215
-8
@@ -24,18 +24,39 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
etcdClient "github.com/coreos/etcd/client"
|
etcdClient "github.com/coreos/etcd/client"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
"github.com/platform9/fission"
|
"github.com/fission/fission"
|
||||||
"github.com/platform9/fission/controller/client"
|
"github.com/fission/fission/controller/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
var g struct {
|
var g struct {
|
||||||
client *client.Client
|
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) {
|
func TestFunctionApi(t *testing.T) {
|
||||||
log.SetFormatter(&log.TextFormatter{DisableColors: true})
|
log.SetFormatter(&log.TextFormatter{DisableColors: true})
|
||||||
|
|
||||||
@@ -50,12 +71,17 @@ func TestFunctionApi(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Code: "code1",
|
Code: "code1",
|
||||||
}
|
}
|
||||||
|
_, err := g.client.FunctionGet(&fission.Metadata{Name: "foo"})
|
||||||
|
assertNotFoundFails(err, "function")
|
||||||
|
|
||||||
m, err := g.client.FunctionCreate(testFunc)
|
m, err := g.client.FunctionCreate(testFunc)
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
uid1 := m.Uid
|
uid1 := m.Uid
|
||||||
//log.Printf("Created function %v: %v", m.Name, 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)
|
code, err := g.client.FunctionGetRaw(m)
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
assert(string(code) == testFunc.Code, "code from FunctionGetRaw must match created function")
|
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,
|
assert(len(funcs) == 2,
|
||||||
"created two functions, but didn't find them")
|
"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"})
|
err = g.client.FunctionDelete(&fission.Metadata{Name: "foo"})
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
err = g.client.FunctionDelete(&fission.Metadata{Name: "bar"})
|
err = g.client.FunctionDelete(&fission.Metadata{Name: "bar"})
|
||||||
panicIf(err)
|
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) {
|
func TestHTTPTriggerApi(t *testing.T) {
|
||||||
testTrigger := &fission.HTTPTrigger{
|
testTrigger := &fission.HTTPTrigger{
|
||||||
Metadata: fission.Metadata{
|
Metadata: fission.Metadata{
|
||||||
@@ -119,10 +214,16 @@ func TestHTTPTriggerApi(t *testing.T) {
|
|||||||
Uid: "",
|
Uid: "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
_, err := g.client.HTTPTriggerGet(&fission.Metadata{Name: "foo"})
|
||||||
|
assertNotFoundFails(err, "trigger")
|
||||||
|
|
||||||
m, err := g.client.HTTPTriggerCreate(testTrigger)
|
m, err := g.client.HTTPTriggerCreate(testTrigger)
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
defer g.client.HTTPTriggerDelete(m)
|
defer g.client.HTTPTriggerDelete(m)
|
||||||
|
|
||||||
|
_, err = g.client.HTTPTriggerCreate(testTrigger)
|
||||||
|
assertNameReuseFails(err, "trigger")
|
||||||
|
|
||||||
tr, err := g.client.HTTPTriggerGet(m)
|
tr, err := g.client.HTTPTriggerGet(m)
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
testTrigger.Metadata.Uid = m.Uid
|
testTrigger.Metadata.Uid = m.Uid
|
||||||
@@ -140,6 +241,10 @@ func TestHTTPTriggerApi(t *testing.T) {
|
|||||||
|
|
||||||
testTrigger.Metadata.Name = "yyy"
|
testTrigger.Metadata.Name = "yyy"
|
||||||
m, err = g.client.HTTPTriggerCreate(testTrigger)
|
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)
|
panicIf(err)
|
||||||
defer g.client.HTTPTriggerDelete(m)
|
defer g.client.HTTPTriggerDelete(m)
|
||||||
|
|
||||||
@@ -156,10 +261,16 @@ func TestEnvironmentApi(t *testing.T) {
|
|||||||
},
|
},
|
||||||
RunContainerImageUrl: "gcr.io/xyz",
|
RunContainerImageUrl: "gcr.io/xyz",
|
||||||
}
|
}
|
||||||
|
_, err := g.client.EnvironmentGet(&fission.Metadata{Name: "foo"})
|
||||||
|
assertNotFoundFails(err, "environment")
|
||||||
|
|
||||||
m, err := g.client.EnvironmentCreate(testEnv)
|
m, err := g.client.EnvironmentCreate(testEnv)
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
defer g.client.EnvironmentDelete(m)
|
defer g.client.EnvironmentDelete(m)
|
||||||
|
|
||||||
|
_, err = g.client.EnvironmentCreate(testEnv)
|
||||||
|
assertNameReuseFails(err, "environment")
|
||||||
|
|
||||||
tr, err := g.client.EnvironmentGet(m)
|
tr, err := g.client.EnvironmentGet(m)
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
testEnv.Metadata.Uid = m.Uid
|
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")
|
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) {
|
func TestMain(m *testing.M) {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
fileStore, ks, rs := getTestResourceStore()
|
fileStore, ks, rs := getTestResourceStore()
|
||||||
defer os.RemoveAll(fileStore.root)
|
defer os.RemoveAll(fileStore.root)
|
||||||
|
|
||||||
api := &API{
|
api := MakeAPI(rs)
|
||||||
FunctionStore: FunctionStore{ResourceStore: *rs},
|
|
||||||
HTTPTriggerStore: HTTPTriggerStore{ResourceStore: *rs},
|
|
||||||
EnvironmentStore: EnvironmentStore{ResourceStore: *rs},
|
|
||||||
}
|
|
||||||
g.client = client.MakeClient("http://localhost:8888")
|
g.client = client.MakeClient("http://localhost:8888")
|
||||||
|
|
||||||
ks.Delete(context.Background(), "Function", &etcdClient.DeleteOptions{Recursive: true})
|
ks.Delete(context.Background(), "Function", &etcdClient.DeleteOptions{Recursive: true})
|
||||||
ks.Delete(context.Background(), "HTTPTrigger", &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(), "Environment", &etcdClient.DeleteOptions{Recursive: true})
|
||||||
|
ks.Delete(context.Background(), "Watch", &etcdClient.DeleteOptions{Recursive: true})
|
||||||
|
|
||||||
go api.Serve(8888)
|
go api.Serve(8888)
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
@@ -208,6 +406,15 @@ func TestMain(m *testing.M) {
|
|||||||
resp, err := http.Get("http://localhost:8888/")
|
resp, err := http.Get("http://localhost:8888/")
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
assert(resp.StatusCode == 200, "http get status code on root")
|
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)
|
_, err = ioutil.ReadAll(resp.Body)
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
|
|
||||||
|
|||||||
+325
-31
@@ -26,9 +26,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/fission/fission"
|
||||||
|
|
||||||
"github.com/platform9/fission"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -80,19 +78,15 @@ func (c *Client) url(relativeUrl string) string {
|
|||||||
|
|
||||||
func (c *Client) handleResponse(resp *http.Response) ([]byte, error) {
|
func (c *Client) handleResponse(resp *http.Response) ([]byte, error) {
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
var errCode int
|
return nil, fission.MakeErrorFromHTTP(resp)
|
||||||
switch resp.StatusCode {
|
}
|
||||||
case 403:
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
errCode = fission.ErrorNotAuthorized
|
return body, err
|
||||||
case 404:
|
}
|
||||||
errCode = fission.ErrorNotFound
|
|
||||||
case 400:
|
func (c *Client) handleCreateResponse(resp *http.Response) ([]byte, error) {
|
||||||
errCode = fission.ErrorInvalidArgument
|
if resp.StatusCode != 201 {
|
||||||
default:
|
return nil, fission.MakeErrorFromHTTP(resp)
|
||||||
errCode = fission.ErrorInternal
|
|
||||||
}
|
|
||||||
return nil, fission.MakeError(errCode,
|
|
||||||
fmt.Sprintf("HTTP error %v", resp.StatusCode))
|
|
||||||
}
|
}
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
return body, err
|
return body, err
|
||||||
@@ -114,12 +108,8 @@ func (c *Client) FunctionCreate(f *fission.Function) (*fission.Metadata, error)
|
|||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := c.handleResponse(resp)
|
body, err := c.handleCreateResponse(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"name": f.Metadata.Name,
|
|
||||||
"err": err,
|
|
||||||
}).Error("Failed to create function")
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,6 +213,7 @@ func (c *Client) FunctionList() ([]fission.Function, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := c.handleResponse(resp)
|
body, err := c.handleResponse(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -250,12 +241,8 @@ func (c *Client) HTTPTriggerCreate(t *fission.HTTPTrigger) (*fission.Metadata, e
|
|||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := c.handleResponse(resp)
|
body, err := c.handleCreateResponse(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"name": t.Metadata.Name,
|
|
||||||
"err": err,
|
|
||||||
}).Error("Failed to create http trigger")
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,6 +321,7 @@ func (c *Client) HTTPTriggerList() ([]fission.HTTPTrigger, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := c.handleResponse(resp)
|
body, err := c.handleResponse(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -361,12 +349,8 @@ func (c *Client) EnvironmentCreate(env *fission.Environment) (*fission.Metadata,
|
|||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := c.handleResponse(resp)
|
body, err := c.handleCreateResponse(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"name": env.Metadata.Name,
|
|
||||||
"err": err,
|
|
||||||
}).Error("Failed to create environment")
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,6 +429,7 @@ func (c *Client) EnvironmentList() ([]fission.Environment, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := c.handleResponse(resp)
|
body, err := c.handleResponse(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -459,3 +444,312 @@ func (c *Client) EnvironmentList() ([]fission.Environment, error) {
|
|||||||
|
|
||||||
return envs, nil
|
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"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/gorilla/mux"
|
"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) {
|
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)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.respondWithError(w, err)
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
defer r.Body.Close()
|
|
||||||
|
|
||||||
var env fission.Environment
|
var env fission.Environment
|
||||||
err = json.Unmarshal(body, &env)
|
err = json.Unmarshal(body, &env)
|
||||||
@@ -71,6 +71,7 @@ func (api *API) EnvironmentApiCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
api.respondWithSuccess(w, resp)
|
api.respondWithSuccess(w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +104,7 @@ func (api *API) EnvironmentApiUpdate(w http.ResponseWriter, r *http.Request) {
|
|||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.respondWithError(w, err)
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var env fission.Environment
|
var env fission.Environment
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"github.com/satori/go.uuid"
|
"github.com/satori/go.uuid"
|
||||||
|
|
||||||
"github.com/platform9/fission"
|
"github.com/fission/fission"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EnvironmentStore struct {
|
type EnvironmentStore struct {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type requestType int
|
type requestType int
|
||||||
@@ -62,6 +62,9 @@ func MakeFileStore(path string) *FileStore {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Printf("Created directory %v", path)
|
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)
|
response.error = ioutil.WriteFile(path.Join(fs.root, req.fileName), req.fileContents, 0600)
|
||||||
case DELETE:
|
case DELETE:
|
||||||
response.error = os.Remove(path.Join(fs.root, req.fileName))
|
response.error = os.Remove(path.Join(fs.root, req.fileName))
|
||||||
|
if os.IsNotExist(response.error) {
|
||||||
|
response.error = nil
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
log.Panic("bad request")
|
log.Panic("bad request")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,15 +17,17 @@ limitations under the License.
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"encoding/json"
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"encoding/base64"
|
"github.com/fission/fission"
|
||||||
"github.com/platform9/fission"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (api *API) FunctionApiList(w http.ResponseWriter, r *http.Request) {
|
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)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.respondWithError(w, err)
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var f fission.Function
|
var f fission.Function
|
||||||
@@ -77,6 +80,7 @@ func (api *API) FunctionApiCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
api.respondWithSuccess(w, resp)
|
api.respondWithSuccess(w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,6 +119,7 @@ func (api *API) FunctionApiUpdate(w http.ResponseWriter, r *http.Request) {
|
|||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.respondWithError(w, err)
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var f fission.Function
|
var f fission.Function
|
||||||
@@ -170,3 +175,33 @@ func (api *API) FunctionApiDelete(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
api.respondWithSuccess(w, []byte(""))
|
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
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/platform9/fission"
|
"github.com/fission/fission"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FunctionStore struct {
|
type FunctionStore struct {
|
||||||
@@ -108,7 +108,26 @@ func (fs *FunctionStore) Delete(m fission.Metadata) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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) {
|
func (fs *FunctionStore) List() ([]fission.Function, error) {
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/gorilla/mux"
|
"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) {
|
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)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.respondWithError(w, err)
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var t fission.HTTPTrigger
|
var t fission.HTTPTrigger
|
||||||
@@ -56,6 +57,20 @@ func (api *API) HTTPTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
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)
|
uid, err := api.HTTPTriggerStore.Create(&t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.respondWithError(w, err)
|
api.respondWithError(w, err)
|
||||||
@@ -69,6 +84,7 @@ func (api *API) HTTPTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
api.respondWithSuccess(w, resp)
|
api.respondWithSuccess(w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +117,7 @@ func (api *API) HTTPTriggerApiUpdate(w http.ResponseWriter, r *http.Request) {
|
|||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.respondWithError(w, err)
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var t fission.HTTPTrigger
|
var t fission.HTTPTrigger
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"github.com/satori/go.uuid"
|
"github.com/satori/go.uuid"
|
||||||
|
|
||||||
"github.com/platform9/fission"
|
"github.com/fission/fission"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HTTPTriggerStore struct {
|
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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/coreos/etcd/client"
|
"github.com/coreos/etcd/client"
|
||||||
"github.com/satori/go.uuid"
|
"github.com/satori/go.uuid"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
"github.com/fission/fission"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -93,7 +97,7 @@ func (rs *ResourceStore) create(r resource) error {
|
|||||||
|
|
||||||
_, err = rs.KeysAPI.Set(context.Background(), key, string(serialized),
|
_, err = rs.KeysAPI.Set(context.Background(), key, string(serialized),
|
||||||
&client.SetOptions{PrevExist: client.PrevNoExist})
|
&client.SetOptions{PrevExist: client.PrevNoExist})
|
||||||
return err
|
return handleEtcdErrorForResource(err, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *ResourceStore) read(rkey string, res resource) error {
|
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)
|
resp, err := rs.KeysAPI.Get(context.Background(), key, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return handleEtcdError(err, typName, rkey)
|
||||||
}
|
}
|
||||||
return rs.serializer.deserialize([]byte(resp.Node.Value), res)
|
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),
|
_, err = rs.KeysAPI.Set(context.Background(), key, string(serialized),
|
||||||
&client.SetOptions{PrevExist: client.PrevExist})
|
&client.SetOptions{PrevExist: client.PrevExist})
|
||||||
return err
|
return handleEtcdErrorForResource(err, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *ResourceStore) delete(typename, rkey string) error {
|
func (rs *ResourceStore) delete(typename, rkey string) error {
|
||||||
key := typename + "/" + rkey
|
key := typename + "/" + rkey
|
||||||
_, err := rs.KeysAPI.Delete(context.Background(), key, nil) // ignore response
|
_, 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
|
// getAll finds all entries under key. If none or found or key
|
||||||
// doesn't exist, returns an empty slice.
|
// doesn't exist, returns an empty slice.
|
||||||
func (rs *ResourceStore) getAll(key string) ([]string, error) {
|
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 err != nil {
|
||||||
if client.IsKeyNotFound(err) {
|
if client.IsKeyNotFound(err) {
|
||||||
return []string{}, nil
|
return []string{}, nil
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, handleEtcdError(err, "", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
res := make([]string, 0, len(resp.Node.Nodes))
|
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)
|
resp, err := rs.KeysAPI.CreateInOrder(context.Background(), parentKey, uid, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = rs.FileStore.delete(uid)
|
_ = rs.FileStore.delete(uid)
|
||||||
return "", "", err
|
return "", "", handleEtcdError(err, "file", parentKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp.Node.Key, uid, nil
|
return resp.Node.Key, uid, nil
|
||||||
@@ -172,7 +176,7 @@ func (rs *ResourceStore) readFile(key string, uid *string) ([]byte, error) {
|
|||||||
key = "file/" + key
|
key = "file/" + key
|
||||||
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Sort: true})
|
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Sort: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, handleEtcdError(err, "file", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
if uid == nil {
|
if uid == nil {
|
||||||
@@ -201,7 +205,7 @@ func (rs *ResourceStore) deleteFile(key string, uid string) error {
|
|||||||
key = "file/" + key
|
key = "file/" + key
|
||||||
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Sort: true})
|
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Sort: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return handleEtcdError(err, "file", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
var node *client.Node
|
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)
|
_, err = rs.KeysAPI.Delete(context.Background(), node.Key, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return handleEtcdError(err, "", node.Key)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(resp.Node.Nodes) == 1 {
|
if len(resp.Node.Nodes) == 1 {
|
||||||
_, err = rs.KeysAPI.Delete(context.Background(), key, &client.DeleteOptions{Dir: true})
|
_, err = rs.KeysAPI.Delete(context.Background(), key, &client.DeleteOptions{Dir: true})
|
||||||
return err
|
return handleEtcdError(err, "file", key)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -236,7 +240,7 @@ func (rs *ResourceStore) deleteAllFiles(key string) error {
|
|||||||
key = "file/" + key
|
key = "file/" + key
|
||||||
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Sort: true})
|
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Sort: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return handleEtcdError(err, "file", key)
|
||||||
}
|
}
|
||||||
for _, u := range resp.Node.Nodes {
|
for _, u := range resp.Node.Nodes {
|
||||||
err = rs.FileStore.delete(u.Value)
|
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)
|
_, err = rs.KeysAPI.Delete(context.Background(), u.Key, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return handleEtcdError(err, "", u.Key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err = rs.KeysAPI.Delete(context.Background(), key, &client.DeleteOptions{Dir: true})
|
_, 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.
|
# A docker image for the func container.
|
||||||
|
|
||||||
FROM node:4-onbuild
|
FROM node:7-onbuild
|
||||||
|
|
||||||
ADD server.js /usr/src/app/server.js
|
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",
|
"name": "fission-nodejs-runtime",
|
||||||
"version": "0.0.0",
|
"version": "0.1.0",
|
||||||
"author": "Soam Vasani",
|
"author": "Soam Vasani",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
{
|
{
|
||||||
"name": "Soam Vasani",
|
"name": "Soam Vasani",
|
||||||
"email": "soamvasani@platform9.com"
|
"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"
|
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"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';
|
'use strict';
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
const process = require('process');
|
const process = require('process');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const app = express();
|
const app = express();
|
||||||
@@ -18,6 +19,16 @@ if (!argv.port) {
|
|||||||
argv.port = 8888;
|
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.
|
// User function. Starts out undefined.
|
||||||
let userFunction;
|
let userFunction;
|
||||||
|
|
||||||
@@ -64,11 +75,13 @@ app.all('/', function (req, res) {
|
|||||||
res.status(500).send("Generic container: no requests supported");
|
res.status(500).send("Generic container: no requests supported");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
request: req,
|
request: req,
|
||||||
response: res
|
response: res
|
||||||
// TODO: context should also have: URL template params, query string
|
// TODO: context should also have: URL template params, query string
|
||||||
};
|
};
|
||||||
|
|
||||||
function callback(status, body, headers) {
|
function callback(status, body, headers) {
|
||||||
if (!status)
|
if (!status)
|
||||||
return;
|
return;
|
||||||
@@ -79,11 +92,32 @@ app.all('/', function (req, res) {
|
|||||||
}
|
}
|
||||||
res.status(status).send(body);
|
res.status(status).send(body);
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
userFunction(context, callback);
|
//
|
||||||
} catch(e) {
|
// Customizing the request context
|
||||||
callback(500, "Internal server error")
|
//
|
||||||
|
// 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);
|
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("headers=", JSON.stringify(context.request.headers));
|
||||||
console.log("body=", JSON.stringify(context.request.body));
|
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
|
FROM alpine:3.5
|
||||||
CMD [ "python", "./server.py" ]
|
|
||||||
EXPOSE 8888
|
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
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
import imp
|
import imp
|
||||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
||||||
|
|
||||||
#
|
from flask import Flask, request, abort, g
|
||||||
# Load the file.
|
|
||||||
#
|
app = Flask(__name__)
|
||||||
|
|
||||||
codepath = '/userfunc/user'
|
codepath = '/userfunc/user'
|
||||||
|
|
||||||
userfunc = None
|
userfunc = None
|
||||||
|
|
||||||
class testHTTPServer_RequestHandler(BaseHTTPRequestHandler):
|
@app.route('/specialize', methods=['POST'])
|
||||||
def do_POST(self):
|
def load():
|
||||||
global userfunc
|
global userfunc
|
||||||
userfunc = (imp.load_source('user', codepath)).main
|
userfunc = (imp.load_source('user', codepath)).main
|
||||||
self.send_response(200)
|
return ""
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(bytes("ok\n", "utf8"))
|
|
||||||
return
|
|
||||||
|
|
||||||
# GET
|
@app.route('/', methods=['GET', 'POST', 'PUT', 'HEAD', 'OPTIONS', 'DELETE'])
|
||||||
def do_GET(self):
|
def f():
|
||||||
global userfunc
|
if userfunc == None:
|
||||||
try:
|
print("Generic container: no requests supported")
|
||||||
print("GET request")
|
abort(500)
|
||||||
message = userfunc(None)
|
#
|
||||||
self.send_response(200)
|
# Customizing the request context
|
||||||
self.end_headers()
|
#
|
||||||
self.wfile.write(bytes(message, "utf8"))
|
# If you want to pass something to the function, you can add it to 'g':
|
||||||
return
|
# g.myKey = myValue
|
||||||
except:
|
# And the user func can then access that (after doing a "from flask import g").
|
||||||
self.send_response(500)
|
#
|
||||||
self.end_headers()
|
return userfunc()
|
||||||
|
|
||||||
def run():
|
#
|
||||||
print('starting server...')
|
# Logging setup. TODO: Loglevel hard-coded for now. We could allow
|
||||||
server_address = ('0.0.0.0', 8888)
|
# functions/routes to override this somehow; or we could create
|
||||||
httpd = HTTPServer(server_address, testHTTPServer_RequestHandler)
|
# separate dev vs. prod environments.
|
||||||
httpd.serve_forever()
|
#
|
||||||
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (e Error) Error() string {
|
func (err Error) Error() string {
|
||||||
return fmt.Sprintf("(Error %v) %v", e.Code, e.Message)
|
return fmt.Sprintf("%v - %v", err.Description(), err.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeError(code int, msg string) Error {
|
func MakeError(code int, msg string) Error {
|
||||||
@@ -36,29 +38,39 @@ func MakeErrorFromHTTP(resp *http.Response) error {
|
|||||||
|
|
||||||
var errCode int
|
var errCode int
|
||||||
switch resp.StatusCode {
|
switch resp.StatusCode {
|
||||||
|
case 400:
|
||||||
|
errCode = ErrorInvalidArgument
|
||||||
case 403:
|
case 403:
|
||||||
errCode = ErrorNotAuthorized
|
errCode = ErrorNotAuthorized
|
||||||
case 404:
|
case 404:
|
||||||
errCode = ErrorNotFound
|
errCode = ErrorNotFound
|
||||||
case 400:
|
case 409:
|
||||||
errCode = ErrorInvalidArgument
|
errCode = ErrorNameExists
|
||||||
default:
|
default:
|
||||||
errCode = ErrorInternal
|
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 {
|
func (err Error) HTTPStatus() int {
|
||||||
var code int
|
var code int
|
||||||
switch err.Code {
|
switch err.Code {
|
||||||
case ErrorNotFound:
|
|
||||||
code = 404
|
|
||||||
case ErrorInvalidArgument:
|
case ErrorInvalidArgument:
|
||||||
code = 400
|
code = 400
|
||||||
case ErrorNoSpace:
|
|
||||||
code = 500
|
|
||||||
case ErrorNotAuthorized:
|
case ErrorNotAuthorized:
|
||||||
code = 403
|
code = 403
|
||||||
|
case ErrorNotFound:
|
||||||
|
code = 404
|
||||||
|
case ErrorNameExists:
|
||||||
|
code = 409
|
||||||
default:
|
default:
|
||||||
code = 500
|
code = 500
|
||||||
}
|
}
|
||||||
@@ -70,11 +82,19 @@ func GetHTTPError(err error) (int, string) {
|
|||||||
var code int
|
var code int
|
||||||
fe, ok := err.(Error)
|
fe, ok := err.(Error)
|
||||||
if ok {
|
if ok {
|
||||||
msg = fe.Message
|
|
||||||
code = fe.HTTPStatus()
|
code = fe.HTTPStatus()
|
||||||
|
msg = fe.Message
|
||||||
} else {
|
} else {
|
||||||
code = 500
|
code = 500
|
||||||
msg = err.Error()
|
msg = err.Error()
|
||||||
}
|
}
|
||||||
return code, msg
|
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) {
|
module.exports = async function(context) {
|
||||||
callback(200, "Hello, world!\n");
|
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';
|
'use strict';
|
||||||
|
|
||||||
var http = require('http');
|
const rp = require('request-promise-native');
|
||||||
|
|
||||||
module.exports = function (context, callback) {
|
module.exports = async function (context) {
|
||||||
let body = context.request.body;
|
const body = context.request.body;
|
||||||
console.log(`body text: ${body['text']}`);
|
console.log(`body = ${body}`);
|
||||||
|
const symbol = body['text'].split(' ')[1];
|
||||||
|
|
||||||
var symbol = body['text'].split(' ')[1];
|
console.log(`Got symbol: ${symbol}`);
|
||||||
|
|
||||||
http.get({
|
if (!symbol) {
|
||||||
host: 'finance.google.com',
|
return {
|
||||||
path: `/finance/info?q=NYSE:${symbol}`
|
status: 400,
|
||||||
}, function(response) {
|
body: {
|
||||||
var resp = '';
|
text: 'You must provide a stock symbol.'
|
||||||
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?)" }`);
|
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
});
|
}
|
||||||
|
|
||||||
|
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