Compare commits

...
29 Commits
Author SHA1 Message Date
Soam Vasani e4d42797e6 set versions to 0.3.0 2017-09-28 23:44:53 -07:00
Soam VasaniandGitHub 6a394889f6 dotnet20 build fixes (#365)
Fix the dotnet20 build, use docker for builds, and re-enable dotnet20 in the release script.
2017-09-28 23:36:11 -07:00
Erwin van EykandSoam Vasani 59c18693fa Fix workflow apiserver proxy (#363) 2017-09-28 22:45:53 -07:00
Erwin van EykandSoam Vasani 0537c4b805 Added experimental deploy script (#364) 2017-09-28 22:19:55 -07:00
Soam VasaniandGitHub 6734bfbf7b Differentiate by environment in fscache eviction (#361) 2017-09-28 10:00:23 -07:00
Soam Vasani 557c2ee6fc Time out post-install hooks in 5 sec 2017-09-27 19:03:45 -07:00
Soam Vasani 52c77c5bf5 Disable dotnet20 build for now (issue #359) 2017-09-27 07:46:00 -07:00
Soam Vasani 63a2cbb417 chmod +x dotnet20/build.sh 2017-09-27 06:55:54 -07:00
Soam Vasani d2f4be7a44 Set version to 0.3.0-rc 2017-09-26 23:35:08 -07:00
Erwin van EykandSoam Vasani 33f967b61b Fission workflow env integration (#336)
Add a flag to the environment to control multiple specialization -- the max number of functions per container. This can be set to 1 or infinity. 

Add an api proxy to workflow apiserver from the controller.

Add function metadata to FunctionLoadRequest; every v2 environment now knows which function it's loading (but can ignore that information if it wants to).

Add function identity headers to router. This is useful for multiple specialization, so the router can disambiguate between different function calls. (If this turns out to be a non-trivial perf overhead, we could add these headers conditionally, but for now they are always added.)
2017-09-26 18:36:07 -07:00
Soam VasaniandGitHub 3f58247b59 Dump package resources at the end of tests (#357) 2017-09-26 12:55:03 -07:00
Erwin van EykandSoam Vasani 45756363bc Add bodyparser for text/* mime types to node-env (#349) 2017-09-25 11:11:43 -07:00
Ta-Ching ChenandSoam Vasani e587eca08f Add builder manager support (#308)
This change orchestrates function builds.

Environments (in v2) define a builder image, just like they do a runtime image. The builder image contains a build script that's invoked with source and deployment paths (as env vars).

The buildermgr watches for environments with build images defined, and creates build deployments and services.

Functions can define source and deployment. Buildermgr watches for functions with source code (and build status == pending) and invokes the environment's builder when appropriate. It captures logs from the build and sets the build status (success/failure) and build lots into the PackageStatus.
2017-09-25 07:53:09 -07:00
Ta-Ching ChenandSoam Vasani a720377f3c Use Containers to find matched storage containers (#350) (#353)
Use stow api correctly to find our function storage container on restarts.
2017-09-24 10:36:06 -07:00
Ta-Ching ChenandSoam Vasani 42819ba362 Fix storage service failed to start after restarting it (#352) 2017-09-24 08:21:13 -07:00
Ta-Ching ChenandSoam Vasani e56e6482b3 Fix missing checksum from CLI uploads (issue #342) (#343)
Archive upload from the CLI wasn't filling in the checksum field, causing validation to fail.
2017-09-22 12:27:42 -07:00
James SturtevantandSoam Vasani 0d252fabcd copy trigger metadata directly from map (#341) 2017-09-22 06:54:05 -07:00
Soam Vasani dedeef7077 Add dotnet20 to release.sh 2017-09-21 11:47:11 -07:00
João AlmeidaandSoam Vasani 7bd3f20ad6 Fission dotnet 2.0 env (#337)
Adding a new dotnet environment, supporting dotnet core 2.0.

Project structure changed. Project.json was abandoned. Project.csproj is now used.
2017-09-21 11:30:15 -07:00
Daijiro WachiandSoam Vasani dc1ffa859d Update a dependency in the package.json (#339)
Since `mz` has already bumped for several times without
breaking change, it's better to follow the updates.

```console
$ npm outdated
Package  Current  Wanted  Latest  Location
mz         2.1.0   2.1.0   2.7.0  fission-nodejs-runtime
```

Fixes: https://github.com/fission/fission/issues/167
2017-09-21 05:55:45 -07:00
Soam VasaniandGitHub c63a427ca3 Fix internal route setup bug (#335)
Fixes #334 which broke internal routes, and adds a test.
2017-09-20 06:51:56 -07:00
Soam VasaniandGitHub e4a4ec4bff Post-install hook to poke analytics function (#325)
Send chart name and chart version after installation to an analytics function. Users can opt-out with --set analytics=false.
2017-09-19 15:39:36 -07:00
TAKAHASHI YutoandSoam Vasani 7190661360 Tag and push the latest environment images (#333)
Update release script to push the latest tag for env images.
2017-09-19 06:34:31 -07:00
Soam VasaniandGitHub 67c38f2f29 Function service cache partial support for multiple specialization (#332)
This adds partial support for multiple specialization to the
function service cache. It allows Add() to succeed if the pod and
address maps already contain entries.

But it doesn't deal with DeletePod and TouchByAddress. That's ok for
now, because the workflow engine never deletes the environment pod
(that pod is the workflow engine).

However, if/when we want full support for multiple specialization,
that will require updating both DeletePod and TouchByAddress.
2017-09-18 15:57:06 -07:00
Myles BorinsandSoam Vasani 23e20115da Upgrade Node Environment to 8.x (#329)
7.x has not been maintained since April. 8.x is going into LTS
during October and will receive another 30 months of support.
2017-09-17 05:11:11 -07:00
Erwin van EykandSoam Vasani 5072a13c7c Removed obsolete Kubernetes YAMLs (#327)
We've switched to helm, and the switch seems to be going well, so that's the only supported installation method now.
2017-09-14 13:34:52 -07:00
Soam Vasani 9d8a46e847 fix broken helm download command in instructions, again 2017-09-12 16:46:39 -07:00
Soam Vasani dca2915a7e fix broken helm download command in instructions 2017-09-12 16:45:33 -07:00
Soam VasaniandGitHub bc02d159f3 update readme with latest install instructions (#324) 2017-09-12 15:16:56 -07:00
87 changed files with 3017 additions and 1271 deletions
+9 -9
View File
@@ -48,7 +48,7 @@ First, you'll need the helm CLI:
On __OS X__: On __OS X__:
``` ```
$ curl -L https://storage.googleapis.com/kubernetes-helm/helm-v2.6.1-darwin-amd64.tar.gz $ curl -LO https://storage.googleapis.com/kubernetes-helm/helm-v2.6.1-darwin-amd64.tar.gz
$ tar xzf helm-v2.6.1-darwin-amd64.tar.gz $ tar xzf helm-v2.6.1-darwin-amd64.tar.gz
@@ -57,7 +57,7 @@ $ mv darwin-amd64/helm /usr/local/bin
On __Linux__: On __Linux__:
``` ```
$ curl -L https://storage.googleapis.com/kubernetes-helm/helm-v2.6.1-linux-amd64.tar.gz $ curl -LO https://storage.googleapis.com/kubernetes-helm/helm-v2.6.1-linux-amd64.tar.gz
$ tar xzf helm-v2.6.1-linux-amd64.tar.gz $ tar xzf helm-v2.6.1-linux-amd64.tar.gz
@@ -84,7 +84,7 @@ role-based access control.)
#### Minikube #### Minikube
``` ```
$ helm install --namespace fission --set serviceType=NodePort https://github.com/fission/fission/releases/download/v0.2.1/fission-all-v0.2.1.tgz $ helm install --namespace fission --set serviceType=NodePort https://github.com/fission/fission/releases/download/0.3.0/fission-all-0.3.0.tgz
``` ```
The serviceType variable allows configuring the type of Kubernetes The serviceType variable allows configuring the type of Kubernetes
@@ -94,7 +94,7 @@ want to expose anything outside the cluster.
#### Cloud hosted clusters (GKE, AWS, Azure etc.) #### Cloud hosted clusters (GKE, AWS, Azure etc.)
``` ```
$ helm install --namespace fission https://github.com/fission/fission/releases/download/v0.2.1/fission-all-v0.2.1.tgz $ helm install --namespace fission https://github.com/fission/fission/releases/download/0.3.0/fission-all-0.3.0.tgz
``` ```
#### Minimal version #### Minimal version
@@ -104,7 +104,7 @@ the NATS message queue, influxDB for logs, etc. If you want a more
minimal setup, you can install the fission-core chart instead: minimal setup, you can install the fission-core chart instead:
``` ```
$ helm install --namespace fission https://github.com/fission/fission/releases/download/v0.2.1/fission-core-v0.2.1.tgz $ helm install --namespace fission https://github.com/fission/fission/releases/download/0.3.0/fission-core-0.3.0.tgz
``` ```
### Install the Fission CLI ### Install the Fission CLI
@@ -114,19 +114,19 @@ $ helm install --namespace fission https://github.com/fission/fission/releases/d
Get the CLI binary for Mac: Get the CLI binary for Mac:
``` ```
$ curl -Lo fission https://github.com/fission/fission/releases/download/v0.2.1/fission-cli-osx && chmod +x fission && sudo mv fission /usr/local/bin/ $ curl -Lo fission https://github.com/fission/fission/releases/download/0.3.0/fission-cli-osx && chmod +x fission && sudo mv fission /usr/local/bin/
``` ```
#### Linux #### Linux
``` ```
$ curl -Lo fission https://github.com/fission/fission/releases/download/v0.2.1/fission-cli-linux && chmod +x fission && sudo mv fission /usr/local/bin/ $ curl -Lo fission https://github.com/fission/fission/releases/download/0.3.0/fission-cli-linux && chmod +x fission && sudo mv fission /usr/local/bin/
``` ```
#### Windows #### Windows
For Windows, you can use the linux binary on WSL. Or you can download 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/v0.2.1/fission-cli-windows.exe) this windows executable: [fission.exe](https://github.com/fission/fission/releases/download/0.3.0/fission-cli-windows.exe)
### Set environment vars ### Set environment vars
@@ -159,7 +159,7 @@ svc```). Then:
Finally, you're ready to use Fission! Finally, you're ready to use Fission!
``` ```
$ fission env create --name nodejs --image fission/node-env:v0.2.1 $ fission env create --name nodejs --image fission/node-env:0.3.0
$ curl -LO https://raw.githubusercontent.com/fission/fission/master/examples/nodejs/hello.js $ curl -LO https://raw.githubusercontent.com/fission/fission/master/examples/nodejs/hello.js
+2 -221
View File
@@ -1,221 +1,2 @@
These instructions are replaced by:
- [Running Fission on your Cluster](#running-fission-on-your-cluster) http://fission.io/docs/v0.2.1/install/
* [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/nightly20170705/fission-rbac.yaml
$ kubectl create -f https://github.com/fission/fission/releases/download/nightly20170705/fission-nodeport.yaml
```
Set the FISSION_URL and FISSION_ROUTER environment variables.
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/nightly20170705/fission-rbac.yaml
$ kubectl create -f https://github.com/fission/fission/releases/download/nightly20170705/fission-cloud.yaml
```
Save the external IP addresses of controller and router services in
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/nightly20170705/fission-openshift.yaml
$ oc create -f https://github.com/fission/fission/releases/download/nightly20170705/fission-nodeport.yaml
```
#### Using other clouds
If you're using any cloud provider that supports the LoadBalancer service type, use these commands:
```
$ oc login -u system:admin
$ oc create -f https://github.com/fission/fission/releases/download/nightly20170705/fission-openshift.yaml
$ oc create -f https://github.com/fission/fission/releases/download/nightly20170705/fission-cloud.yaml
```
After these steps, you should be able to run fission client as with kubernetes.
### Install the client CLI
#### Mac OS
Get the CLI binary for Mac:
```
$ curl -Lo fission https://github.com/fission/fission/releases/download/nightly20170705/fission-cli-osx && chmod +x fission && sudo mv fission /usr/local/bin/
```
#### Linux
```
$ curl -Lo fission https://github.com/fission/fission/releases/download/nightly20170705/fission-cli-linux && chmod +x fission && sudo mv fission /usr/local/bin/
```
#### Windows
For Windows, you can use the linux binary on WSL. Or you can download
this windows executable: [fission.exe](https://github.com/fission/fission/releases/download/nightly20170705/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 https://github.com/fission/fission/releases/download/nightly20170705/fission-logger.yaml
```
That's it for the basic setup. You can now use following command to view function logs:
```
$ 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 https://github.com/fission/fission/releases/download/nightly20170705/fission-nats.yaml
```
You can subscribe to a NATS Streaming queue with a command like this:
(See `fission mqtrigger --help` for details)
```
$ fission mqtrigger create --name myQueueTrigger --function processEvent --topic "myQueue.request"
```
+1 -1
View File
@@ -81,7 +81,7 @@ See the [examples](examples) directory for more.
Running Fission on your Cluster Running Fission on your Cluster
=============================== ===============================
See the [installation guide](INSTALL.md). See the [installation guide](http://fission.io/docs/v0.2.1/install/).
Compiling Fission Compiling Fission
================= =================
+6 -1
View File
@@ -98,7 +98,12 @@ func (builder *Builder) Handler(w http.ResponseWriter, r *http.Request) {
srcPkgPath := filepath.Join(builder.sharedVolumePath, req.SrcPkgFilename) srcPkgPath := filepath.Join(builder.sharedVolumePath, req.SrcPkgFilename)
deployPkgFilename := fmt.Sprintf("%v-%v", req.SrcPkgFilename, strings.ToLower(uniuri.NewLen(6))) deployPkgFilename := fmt.Sprintf("%v-%v", req.SrcPkgFilename, strings.ToLower(uniuri.NewLen(6)))
deployPkgPath := filepath.Join(builder.sharedVolumePath, deployPkgFilename) deployPkgPath := filepath.Join(builder.sharedVolumePath, deployPkgFilename)
buildLogs, err := builder.build(req.BuildCommand, srcPkgPath, deployPkgPath) buildCmd := req.BuildCommand
if len(buildCmd) == 0 {
// use default build command
buildCmd = "/build"
}
buildLogs, err := builder.build(buildCmd, srcPkgPath, deployPkgPath)
if err != nil { if err != nil {
e := errors.New(fmt.Sprintf("Error building source package: %v", err)) e := errors.New(fmt.Sprintf("Error building source package: %v", err))
http.Error(w, e.Error(), 500) http.Error(w, e.Error(), 500)
+20 -7
View File
@@ -19,6 +19,7 @@ package client
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"io/ioutil"
"net/http" "net/http"
"strings" "strings"
@@ -32,25 +33,37 @@ type (
} }
) )
func MakeClient(serverUrl string) *Client { func MakeClient(builderUrl string) *Client {
return &Client{ return &Client{
url: strings.TrimSuffix(serverUrl, "/"), url: strings.TrimSuffix(builderUrl, "/"),
} }
} }
func (c *Client) Build(req *builder.PackageBuildRequest) error { func (c *Client) Build(req *builder.PackageBuildRequest) (*builder.PackageBuildResponse, error) {
body, err := json.Marshal(req) body, err := json.Marshal(req)
if err != nil { if err != nil {
return err return nil, err
} }
resp, err := http.Post(c.url, "application/json", bytes.NewReader(body)) resp, err := http.Post(c.url, "application/json", bytes.NewReader(body))
if err != nil { if err != nil {
return err return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
return fission.MakeErrorFromHTTP(resp) return nil, fission.MakeErrorFromHTTP(resp)
} }
return nil
rBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
pkgBuildResp := builder.PackageBuildResponse{}
err = json.Unmarshal([]byte(rBody), &pkgBuildResp)
if err != nil {
return nil, err
}
return &pkgBuildResp, nil
} }
-5
View File
@@ -1,5 +0,0 @@
FROM alpine:3.4
ADD builder /
EXPOSE 8000
+3 -2
View File
@@ -1,2 +1,3 @@
#!/bin/sh #!/bin/bash
GOOS=linux GOARCH=386 go build -o builder . CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o builder .
+1 -1
View File
@@ -38,5 +38,5 @@ func main() {
builder := builder.MakeBuilder(dir) builder := builder.MakeBuilder(dir)
mux := http.NewServeMux() mux := http.NewServeMux()
mux.HandleFunc("/", builder.Handler) mux.HandleFunc("/", builder.Handler)
http.ListenAndServe(":8000", mux) http.ListenAndServe(":8001", mux)
} }
+108
View File
@@ -0,0 +1,108 @@
/*
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 buildermgr
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"k8s.io/client-go/1.5/kubernetes"
"k8s.io/client-go/1.5/pkg/api"
"github.com/fission/fission"
"github.com/fission/fission/tpr"
)
type (
BuildRequest struct {
Package api.ObjectMeta `json:"package"`
}
BuilderMgr struct {
fissionClient *tpr.FissionClient
kubernetesClient *kubernetes.Clientset
storageSvcUrl string
namespace string
}
)
func MakeBuilderMgr(fissionClient *tpr.FissionClient,
kubernetesClient *kubernetes.Clientset, storageSvcUrl string,
envBuilderNamespace string) *BuilderMgr {
envWatcher := makeEnvironmentWatcher(fissionClient, kubernetesClient, envBuilderNamespace)
go envWatcher.watchEnvironments()
pkgWatcher := makePackageWatcher(fissionClient, kubernetesClient, envBuilderNamespace, storageSvcUrl)
go pkgWatcher.watchPackages()
return &BuilderMgr{
fissionClient: fissionClient,
kubernetesClient: kubernetesClient,
storageSvcUrl: storageSvcUrl,
namespace: envBuilderNamespace,
}
}
func (builderMgr *BuilderMgr) build(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
e := fmt.Sprintf("Failed to read request: %v", err)
log.Println(e)
http.Error(w, e, 500)
return
}
buildReq := BuildRequest{}
err = json.Unmarshal([]byte(body), &buildReq)
if err != nil {
e := fmt.Sprintf("invalid request body: %v", err)
log.Println(e)
http.Error(w, e, 400)
return
}
buildLogs, err := buildPackage(builderMgr.fissionClient, builderMgr.kubernetesClient,
builderMgr.namespace, builderMgr.storageSvcUrl, buildReq)
if err != nil {
code, e := fission.GetHTTPError(err)
http.Error(w, e, code)
return
}
w.Header().Set("Content-Type", "text/plain")
_, err = w.Write([]byte(buildLogs))
if err != nil {
e := fmt.Sprintf("Failed to reply http request: %v", err)
log.Println(e)
http.Error(w, e, 500)
}
}
func (builderMgr *BuilderMgr) Serve(port int) {
r := mux.NewRouter()
r.HandleFunc("/v1/build", builderMgr.build).Methods("POST")
address := fmt.Sprintf(":%v", port)
log.Printf("Start buildermgr at port %v", address)
log.Fatal(http.ListenAndServe(address, handlers.LoggingHandler(os.Stdout, r)))
}
+39
View File
@@ -0,0 +1,39 @@
/*
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 buildermgr
import (
"log"
"github.com/fission/fission/tpr"
)
// Start the buildermgr service.
func Start(port int, storageSvcUrl string, envBuilderNamespace string) error {
fissionClient, kubernetesClient, err := tpr.MakeFissionClient()
if err != nil {
log.Printf("Failed to get kubernetes client: %v", err)
return err
}
api := MakeBuilderMgr(fissionClient, kubernetesClient,
storageSvcUrl, envBuilderNamespace)
go api.Serve(port)
return nil
}
+61
View File
@@ -0,0 +1,61 @@
/*
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 client
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"strings"
"github.com/fission/fission"
"github.com/fission/fission/buildermgr"
)
type (
Client struct {
url string
}
)
func MakeClient(builderUrl string) *Client {
return &Client{
url: strings.TrimSuffix(builderUrl, "/") + "/v1",
}
}
func (c *Client) PackageBuild(req *buildermgr.BuildRequest) ([]byte, error) {
body, err := json.Marshal(req)
if err != nil {
return nil, err
}
resp, err := http.Post(c.url+"/build", "application/json", bytes.NewReader(body))
if err != nil {
return nil, err
}
return c.handleResponse(resp)
}
func (c *Client) handleResponse(resp *http.Response) ([]byte, error) {
if resp.StatusCode != 200 {
return nil, fission.MakeErrorFromHTTP(resp)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
return body, err
}
+211
View File
@@ -0,0 +1,211 @@
/*
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 buildermgr
import (
"fmt"
"log"
"strings"
"k8s.io/client-go/1.5/kubernetes"
"k8s.io/client-go/1.5/pkg/api"
"github.com/dchest/uniuri"
"github.com/fission/fission"
"github.com/fission/fission/builder"
builderClient "github.com/fission/fission/builder/client"
"github.com/fission/fission/environments/fetcher"
fetcherClient "github.com/fission/fission/environments/fetcher/client"
"github.com/fission/fission/tpr"
)
// buildPackage helps to build source package into deployment package.
// Following is steps buildPackage takes to complete the build process.
// 1. Check package status
// 2. Update package status to running state
// 3. Send fetch request to fetcher to fetch source package.
// 4. Send build request to builder to start a build.
// 5. Send upload request to fetcher to upload deployment package.
// 6. Update package status to succeed state
// 7. Update package resource in package ref of functions that share the same package
// *. Update package status to failed state,if any one of steps above failed
func buildPackage(fissionClient *tpr.FissionClient, kubernetesClient *kubernetes.Clientset,
builderNamespace string, storageSvcUrl string, buildReq BuildRequest) (buildLogs string, err error) {
pkg, err := fissionClient.Packages(
buildReq.Package.Namespace).Get(buildReq.Package.Name)
if err != nil {
e := fmt.Sprintf("Error getting function TPR info: %v", err)
log.Println(e)
updatePackage(fissionClient, pkg, fission.BuildStatusFailed, e, nil)
return e, fission.MakeError(500, e)
}
// Only do build for pending packages
if pkg.Status.BuildStatus != fission.BuildStatusPending {
e := "package is not in pending state"
log.Println(e)
return e, fission.MakeError(400, e)
}
// update package status to running state, so that
// we can know what status a package is through cli.
_, err = updatePackage(fissionClient, pkg, fission.BuildStatusRunning, "", nil)
if err != nil {
e := fmt.Sprintf("Error setting package pending state: %v", err)
log.Println(e)
updatePackage(fissionClient, pkg, fission.BuildStatusFailed, e, nil)
return e, fission.MakeError(500, e)
}
env, err := fissionClient.Environments(api.NamespaceDefault).Get(pkg.Spec.Environment.Name)
if err != nil {
e := fmt.Sprintf("Error getting environment TPR info: %v", err)
log.Println(e)
updatePackage(fissionClient, pkg, fission.BuildStatusFailed, e, nil)
return e, fission.MakeError(500, e)
}
svcName := fmt.Sprintf("%v-%v.%v", env.Metadata.Name, env.Metadata.ResourceVersion, builderNamespace)
srcPkgFilename := fmt.Sprintf("%v-%v", pkg.Metadata.Name, strings.ToLower(uniuri.NewLen(6)))
fetcherC := fetcherClient.MakeClient(fmt.Sprintf("http://%v:8000", svcName))
builderC := builderClient.MakeClient(fmt.Sprintf("http://%v:8001", svcName))
fetchReq := &fetcher.FetchRequest{
FetchType: fetcher.FETCH_SOURCE,
Package: pkg.Metadata,
Filename: srcPkgFilename,
}
// send fetch request to fetcher
err = fetcherC.Fetch(fetchReq)
if err != nil {
e := fmt.Sprintf("Error fetching source package: %v", err)
log.Println(e)
updatePackage(fissionClient, pkg, fission.BuildStatusFailed, e, nil)
return e, fission.MakeError(500, e)
}
pkgBuildReq := &builder.PackageBuildRequest{
SrcPkgFilename: srcPkgFilename,
BuildCommand: pkg.Spec.BuildCommand,
}
log.Printf("Start building with source package: %v", srcPkgFilename)
// send build request to builder
buildResp, err := builderC.Build(pkgBuildReq)
if err != nil {
e := fmt.Sprintf("Error building deployment package: %v", err)
log.Println(e)
updatePackage(fissionClient, pkg, fission.BuildStatusFailed, e, nil)
return e, fission.MakeError(500, e)
}
log.Printf("Build succeed, source package: %v, deployment package: %v", srcPkgFilename, buildResp.ArtifactFilename)
uploadReq := &fetcher.UploadRequest{
Filename: buildResp.ArtifactFilename,
StorageSvcUrl: storageSvcUrl,
}
log.Printf("Start uploading deployment package: %v", buildResp.ArtifactFilename)
// ask fetcher to upload the deployment package
uploadResp, err := fetcherC.Upload(uploadReq)
if err != nil {
e := fmt.Sprintf("Error uploading deployment package: %v", err)
log.Println(e)
updatePackage(fissionClient, pkg, fission.BuildStatusFailed, e, nil)
return e, fission.MakeError(500, e)
}
log.Printf("Start updating info of package: %v", pkg.Metadata.Name)
// update package status and also build logs
newPkgRV, err := updatePackage(fissionClient, pkg,
fission.BuildStatusSucceeded, buildResp.BuildLogs, uploadResp)
if err != nil {
e := fmt.Sprintf("Error creating deployment package TPR resource: %v", err)
log.Println(e)
updatePackage(fissionClient, pkg, fission.BuildStatusFailed, e, nil)
return e, fission.MakeError(500, e)
}
fnList, err := fissionClient.
Functions(api.NamespaceDefault).List(api.ListOptions{})
if err != nil {
e := fmt.Sprintf("Error getting function list: %v", err)
log.Println(e)
updatePackage(fissionClient, pkg, fission.BuildStatusFailed, e, nil)
return e, fission.MakeError(500, e)
}
// A package may be used by multiple functions. Update
// functions with old package resource version
for _, fn := range fnList.Items {
if fn.Spec.Package.PackageRef.Name == pkg.Metadata.Name &&
fn.Spec.Package.PackageRef.Namespace == pkg.Metadata.Namespace &&
fn.Spec.Package.PackageRef.ResourceVersion != pkg.Metadata.ResourceVersion {
fn.Spec.Package.PackageRef.ResourceVersion = newPkgRV
// update TPR
_, err = fissionClient.Functions(fn.Metadata.Namespace).Update(&fn)
if err != nil {
e := fmt.Sprintf("Error updating function package resource version: %v", err)
log.Println(e)
updatePackage(fissionClient, pkg, fission.BuildStatusFailed, e, nil)
return e, fission.MakeError(500, e)
}
}
}
log.Printf("Completed build request for package: %v", pkg.Metadata.Name)
return buildResp.BuildLogs, nil
}
func updatePackage(fissionClient *tpr.FissionClient,
pkg *tpr.Package, status fission.BuildStatus, buildLogs string,
uploadResp *fetcher.UploadResponse) (string, error) {
// Kubernetes checks resource version before applying
// new resource config. The update operation will be
// rejected if the resource version in metadata is lower
// than the latest version. Set resource version empty
// string to skip resource version check.
pkg.Metadata.ResourceVersion = ""
pkg.Status = fission.PackageStatus{
BuildStatus: status,
BuildLog: buildLogs,
}
if uploadResp != nil {
pkg.Spec.Deployment = fission.Archive{
Type: fission.ArchiveTypeUrl,
URL: uploadResp.ArchiveDownloadUrl,
Checksum: uploadResp.Checksum,
}
}
// update package spec
pkg, err := fissionClient.Packages(api.NamespaceDefault).Update(pkg)
if err != nil {
log.Printf("Error updating package: %v", err)
return "", err
}
// return resource version for function to update function package ref
return pkg.Metadata.ResourceVersion, nil
}
+477
View File
@@ -0,0 +1,477 @@
/*
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 buildermgr
import (
"fmt"
"log"
"os"
"time"
"k8s.io/client-go/1.5/kubernetes"
"k8s.io/client-go/1.5/pkg/api"
"k8s.io/client-go/1.5/pkg/api/v1"
"k8s.io/client-go/1.5/pkg/apis/extensions/v1beta1"
"k8s.io/client-go/1.5/pkg/labels"
"k8s.io/client-go/1.5/pkg/util/intstr"
"k8s.io/client-go/1.5/pkg/watch"
"github.com/fission/fission/tpr"
)
type requestType int
const (
GET_BUILDER requestType = iota
CLEANUP_BUILDERS
LABEL_ENV_NAME = "envName"
LABEL_ENV_RESOURCEVERSION = "envResourceVersion"
)
type (
builderInfo struct {
envMetadata *api.ObjectMeta
deployment *v1beta1.Deployment
service *v1.Service
}
envwRequest struct {
requestType
env *tpr.Environment
envList []tpr.Environment
respChan chan envwResponse
}
envwResponse struct {
builderInfo *builderInfo
err error
}
environmentWatcher struct {
cache map[string]*builderInfo
requestChan chan envwRequest
builderNamespace string
fissionClient *tpr.FissionClient
kubernetesClient *kubernetes.Clientset
fetcherImage string
fetcherImagePullPolicy v1.PullPolicy
}
)
func makeEnvironmentWatcher(fissionClient *tpr.FissionClient,
kubernetesClient *kubernetes.Clientset, builderNamespace string) *environmentWatcher {
fetcherImage := os.Getenv("FETCHER_IMAGE")
if len(fetcherImage) == 0 {
fetcherImage = "fission/fetcher"
}
fetcherImagePullPolicy := os.Getenv("FETCHER_IMAGE_PULL_POLICY")
if len(fetcherImagePullPolicy) == 0 {
fetcherImagePullPolicy = "IfNotPresent"
}
var pullPolicy v1.PullPolicy
switch fetcherImagePullPolicy {
case "Always":
pullPolicy = v1.PullAlways
case "Never":
pullPolicy = v1.PullNever
default:
pullPolicy = v1.PullIfNotPresent
}
envWatcher := &environmentWatcher{
cache: make(map[string]*builderInfo),
requestChan: make(chan envwRequest),
builderNamespace: builderNamespace,
fissionClient: fissionClient,
kubernetesClient: kubernetesClient,
fetcherImage: fetcherImage,
fetcherImagePullPolicy: pullPolicy,
}
go envWatcher.service()
return envWatcher
}
func (envw *environmentWatcher) getCacheKey(envName string, envResourceVersion string) string {
return fmt.Sprintf("%v-%v", envName, envResourceVersion)
}
func (envw *environmentWatcher) getLabels(envName string, envResourceVersion string) map[string]string {
return map[string]string{
LABEL_ENV_NAME: envName,
LABEL_ENV_RESOURCEVERSION: envResourceVersion,
}
}
func (envw *environmentWatcher) watchEnvironments() {
rv := ""
for {
wi, err := envw.fissionClient.Environments(api.NamespaceAll).Watch(api.ListOptions{
ResourceVersion: rv,
})
if err != nil {
log.Fatalf("Error watching environment list: %v", err)
}
for {
ev, more := <-wi.ResultChan()
if !more {
// restart watch from last rv
break
}
if ev.Type == watch.Error {
// restart watch from the start
rv = ""
time.Sleep(time.Second)
break
}
env := ev.Object.(*tpr.Environment)
rv = env.Metadata.ResourceVersion
envw.sync()
}
}
}
func (envw *environmentWatcher) sync() {
envList, err := envw.fissionClient.Environments(api.NamespaceAll).List(api.ListOptions{})
if err != nil {
log.Fatalf("Error syncing environment TPR resources: %v", err)
}
// Create environment builders for all environments
for i := range envList.Items {
env := envList.Items[i]
if env.Spec.Version == 1 || // builder is not supported with v1 interface
len(env.Spec.Builder.Image) == 0 { // ignore env without builder image
continue
}
_, err := envw.getEnvBuilder(&env)
if err != nil {
log.Printf("Error creating builder for %v: %v", env.Metadata.Name, err)
}
}
envw.cleanupEnvBuilders(envList.Items)
}
func (envw *environmentWatcher) service() {
for {
req := <-envw.requestChan
switch req.requestType {
case GET_BUILDER:
key := envw.getCacheKey(req.env.Metadata.Name, req.env.Metadata.ResourceVersion)
builderInfo, ok := envw.cache[key]
if !ok {
builderInfo, err := envw.createBuilder(req.env)
if err != nil {
req.respChan <- envwResponse{err: err}
continue
}
envw.cache[key] = builderInfo
}
req.respChan <- envwResponse{builderInfo: builderInfo}
case CLEANUP_BUILDERS:
latestEnvList := make(map[string]*tpr.Environment)
for i := range req.envList {
env := req.envList[i]
key := envw.getCacheKey(env.Metadata.Name, env.Metadata.ResourceVersion)
latestEnvList[key] = &env
}
// If an environment is deleted when builder manager down,
// the builder belongs to the environment will be out-of-
// control (an orphan builder) since there is no record in
// cache and TPR. We need to iterate over the services &
// deployments to remove both normal and orphan builders.
svcList, err := envw.getBuilderServiceList(nil)
if err != nil {
log.Println(err.Error())
}
for _, svc := range svcList {
envName := svc.ObjectMeta.Labels[LABEL_ENV_NAME]
envResourceVersion := svc.ObjectMeta.Labels[LABEL_ENV_RESOURCEVERSION]
key := envw.getCacheKey(envName, envResourceVersion)
if _, ok := latestEnvList[key]; !ok {
err := envw.deleteBuilderService(svc.ObjectMeta.Labels)
if err != nil {
log.Printf("Error removing builder service: %v", err)
}
}
delete(envw.cache, svc.ObjectMeta.Name)
}
deployList, err := envw.getBuilderDeploymentList(nil)
if err != nil {
log.Printf(err.Error())
}
for _, deploy := range deployList {
envName := deploy.ObjectMeta.Labels[LABEL_ENV_NAME]
envResourceVersion := deploy.ObjectMeta.Labels[LABEL_ENV_RESOURCEVERSION]
key := envw.getCacheKey(envName, envResourceVersion)
if _, ok := latestEnvList[key]; !ok {
err := envw.deleteBuilderDeployment(deploy.ObjectMeta.Labels)
if err != nil {
log.Printf("Error removing builder deployment: %v", err)
}
}
delete(envw.cache, deploy.ObjectMeta.Name)
}
}
}
}
func (envw *environmentWatcher) getEnvBuilder(env *tpr.Environment) (*builderInfo, error) {
respChan := make(chan envwResponse)
envw.requestChan <- envwRequest{
requestType: GET_BUILDER,
env: env,
respChan: respChan,
}
resp := <-respChan
return resp.builderInfo, resp.err
}
func (envw *environmentWatcher) cleanupEnvBuilders(envs []tpr.Environment) {
envw.requestChan <- envwRequest{
requestType: CLEANUP_BUILDERS,
envList: envs,
}
}
func (envw *environmentWatcher) createBuilder(env *tpr.Environment) (*builderInfo, error) {
var svc *v1.Service
var deploy *v1beta1.Deployment
sel := envw.getLabels(env.Metadata.Name, env.Metadata.ResourceVersion)
svcList, err := envw.getBuilderServiceList(sel)
if err != nil {
return nil, err
}
if len(svcList) == 0 {
svc, err = envw.createBuilderService(env)
if err != nil {
return nil, fmt.Errorf("Error creating builder service: %v", err)
}
}
deployList, err := envw.getBuilderDeploymentList(sel)
if err != nil {
return nil, err
}
if len(deployList) == 0 {
deploy, err = envw.createBuilderDeployment(env)
if err != nil {
return nil, fmt.Errorf("Error creating builder deployment: %v", err)
}
}
return &builderInfo{
envMetadata: &env.Metadata,
service: svc,
deployment: deploy,
}, nil
}
func (envw *environmentWatcher) deleteBuilderService(sel map[string]string) error {
svcList, err := envw.getBuilderServiceList(sel)
if err != nil {
return err
}
for _, svc := range svcList {
log.Printf("Removing builder service: %v", svc.ObjectMeta.Name)
// cascading deletion
// https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/
falseVal := false
delOpt := &api.DeleteOptions{
OrphanDependents: &falseVal,
}
err = envw.kubernetesClient.
Services(envw.builderNamespace).
Delete(svc.ObjectMeta.Name, delOpt)
if err != nil {
return fmt.Errorf("Error deleting builder service: %v", err)
}
}
return nil
}
func (envw *environmentWatcher) deleteBuilderDeployment(sel map[string]string) error {
deployList, err := envw.getBuilderDeploymentList(sel)
if err != nil {
return err
}
for _, deploy := range deployList {
log.Printf("Removing builder deployment: %v", deploy.ObjectMeta.Name)
falseVal := false
delOpt := &api.DeleteOptions{
OrphanDependents: &falseVal,
}
err = envw.kubernetesClient.
Deployments(envw.builderNamespace).
Delete(deploy.ObjectMeta.Name, delOpt)
if err != nil {
return fmt.Errorf("Error deleteing builder deployment: %v", err)
}
}
return nil
}
func (envw *environmentWatcher) getBuilderServiceList(sel map[string]string) ([]v1.Service, error) {
svcList, err := envw.kubernetesClient.Services(envw.builderNamespace).List(
api.ListOptions{
LabelSelector: labels.Set(sel).AsSelector(),
})
if err != nil {
return nil, fmt.Errorf("Error getting builder service list: %v", err)
}
return svcList.Items, nil
}
func (envw *environmentWatcher) createBuilderService(env *tpr.Environment) (*v1.Service, error) {
name := envw.getCacheKey(env.Metadata.Name, env.Metadata.ResourceVersion)
sel := envw.getLabels(env.Metadata.Name, env.Metadata.ResourceVersion)
service := v1.Service{
ObjectMeta: v1.ObjectMeta{
Namespace: envw.builderNamespace,
Name: name,
Labels: sel,
},
Spec: v1.ServiceSpec{
Selector: sel,
Type: v1.ServiceTypeClusterIP,
Ports: []v1.ServicePort{
{
Name: "fetcher-port",
Protocol: v1.ProtocolTCP,
Port: 8000,
TargetPort: intstr.IntOrString{
Type: intstr.Int,
IntVal: 8000,
},
},
{
Name: "builder-port",
Protocol: v1.ProtocolTCP,
Port: 8001,
TargetPort: intstr.IntOrString{
Type: intstr.Int,
IntVal: 8001,
},
},
},
},
}
log.Printf("Creating builder service: %v", name)
_, err := envw.kubernetesClient.Services(envw.builderNamespace).Create(&service)
if err != nil {
return nil, err
}
return &service, nil
}
func (envw *environmentWatcher) getBuilderDeploymentList(sel map[string]string) ([]v1beta1.Deployment, error) {
deployList, err := envw.kubernetesClient.Deployments(envw.builderNamespace).List(
api.ListOptions{
LabelSelector: labels.Set(sel).AsSelector(),
})
if err != nil {
return nil, fmt.Errorf("Error getting builder deployment list: %v", err)
}
return deployList.Items, nil
}
func (envw *environmentWatcher) createBuilderDeployment(env *tpr.Environment) (*v1beta1.Deployment, error) {
sharedMountPath := "/package"
name := envw.getCacheKey(env.Metadata.Name, env.Metadata.ResourceVersion)
sel := envw.getLabels(env.Metadata.Name, env.Metadata.ResourceVersion)
var replicas int32 = 1
deployment := &v1beta1.Deployment{
ObjectMeta: v1.ObjectMeta{
Namespace: envw.builderNamespace,
Name: name,
Labels: sel,
},
Spec: v1beta1.DeploymentSpec{
Replicas: &replicas,
Selector: &v1beta1.LabelSelector{
MatchLabels: sel,
},
Template: v1.PodTemplateSpec{
ObjectMeta: v1.ObjectMeta{
Labels: sel,
},
Spec: v1.PodSpec{
Volumes: []v1.Volume{
{
Name: "package",
VolumeSource: v1.VolumeSource{
EmptyDir: &v1.EmptyDirVolumeSource{},
},
},
},
Containers: []v1.Container{
{
Name: "builder",
Image: env.Spec.Builder.Image,
ImagePullPolicy: v1.PullAlways,
TerminationMessagePath: "/dev/termination-log",
VolumeMounts: []v1.VolumeMount{
{
Name: "package",
MountPath: sharedMountPath,
},
},
Command: []string{"/builder", sharedMountPath},
},
{
Name: "fetcher",
Image: envw.fetcherImage,
ImagePullPolicy: envw.fetcherImagePullPolicy,
TerminationMessagePath: "/dev/termination-log",
VolumeMounts: []v1.VolumeMount{
{
Name: "package",
MountPath: sharedMountPath,
},
},
Command: []string{"/fetcher", sharedMountPath},
},
},
ServiceAccountName: "fission-builder",
},
},
},
}
log.Printf("Creating builder deployment: %v", envw.getCacheKey(env.Metadata.Name, env.Metadata.ResourceVersion))
_, err := envw.kubernetesClient.Deployments(envw.builderNamespace).Create(deployment)
if err != nil {
return nil, err
}
return deployment, nil
}
+91
View File
@@ -0,0 +1,91 @@
/*
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 buildermgr
import (
"log"
"time"
"k8s.io/client-go/1.5/kubernetes"
"k8s.io/client-go/1.5/pkg/api"
"k8s.io/client-go/1.5/pkg/watch"
"github.com/fission/fission"
"github.com/fission/fission/tpr"
)
type (
packageWatcher struct {
fissionClient *tpr.FissionClient
kubernetesClient *kubernetes.Clientset
builderNamespace string
storageSvcUrl string
}
)
func makePackageWatcher(fissionClient *tpr.FissionClient,
kubernetesClient *kubernetes.Clientset, builderNamespace string, storageSvcUrl string) *packageWatcher {
pkgw := &packageWatcher{
fissionClient: fissionClient,
kubernetesClient: kubernetesClient,
builderNamespace: builderNamespace,
storageSvcUrl: storageSvcUrl,
}
return pkgw
}
func (pkgw *packageWatcher) build(pkgMetadata api.ObjectMeta) {
buildReq := BuildRequest{
Package: pkgMetadata,
}
_, err := buildPackage(pkgw.fissionClient,
pkgw.kubernetesClient, pkgw.builderNamespace, pkgw.storageSvcUrl, buildReq)
if err != nil {
log.Printf("Error building package %v: %v", buildReq.Package.Name, err)
}
}
func (pkgw *packageWatcher) watchPackages() {
rv := ""
for {
wi, err := pkgw.fissionClient.Packages(api.NamespaceDefault).Watch(api.ListOptions{
ResourceVersion: rv,
})
if err != nil {
log.Fatalf("Error watching package TPR resources: %v", err)
}
for {
ev, more := <-wi.ResultChan()
if !more {
break
}
if ev.Type == watch.Error {
rv = ""
time.Sleep(time.Second)
break
}
pkg := ev.Object.(*tpr.Package)
rv = pkg.Metadata.ResourceVersion
// only do build for packages in pending state
if pkg.Status.BuildStatus == fission.BuildStatusPending {
go pkgw.build(pkg.Metadata)
}
}
}
}
+1 -2
View File
@@ -17,7 +17,6 @@ limitations under the License.
package cache package cache
import ( import (
"errors"
"fmt" "fmt"
"time" "time"
@@ -114,7 +113,7 @@ func (c *Cache) service() {
val := c.cache[req.key] val := c.cache[req.key]
val.atime = time.Now() val.atime = time.Now()
resp.existingValue = val.value resp.existingValue = val.value
resp.error = errors.New("value already exists") resp.error = fission.MakeError(fission.ErrorNameExists, "key already exists")
} else { } else {
c.cache[req.key] = &Value{ c.cache[req.key] = &Value{
value: req.value, value: req.value,
+1
View File
@@ -47,6 +47,7 @@ The following table lists the configurable parameters of the Fission chart and t
| `controllerPort` | Fission Controller Service Port | `31313` | | `controllerPort` | Fission Controller Service Port | `31313` |
| `routerPort` | Fission Router Service Port | `31314` | | `routerPort` | Fission Router Service Port | `31314` |
| `functionNamespace` | Namespace for Fission functions | `fission-function` | | `functionNamespace` | Namespace for Fission functions | `fission-function` |
| `builderNamespace` | Namespace for Fission environment builders | `fission-builder` |
| `openshift` | RBAC for openshift | `false` | | `openshift` | RBAC for openshift | `false` |
+2 -2
View File
@@ -1,6 +1,6 @@
apiVersion: v1 apiVersion: v1
name: fission-all name: fission-all
version: 0.2.1 version: 0.3.0
description: Fission is a fast serverless framework for Kubernetes. description: Fission is a fast serverless framework for Kubernetes.
keywords: keywords:
- fission - fission
@@ -12,4 +12,4 @@ maintainers:
- name: Ta Ching Chen - name: Ta Ching Chen
email: contact@tachingchen.com email: contact@tachingchen.com
engine: gotpl engine: gotpl
appVersion: 0.2.1 appVersion: 0.3.0
+3 -3
View File
@@ -1,13 +1,13 @@
1. Install the client CLI. 1. Install the client CLI.
Mac: Mac:
$ curl -Lo fission https://github.com/fission/fission/releases/download/v0.2.1/fission-cli-osx && chmod +x fission && sudo mv fission /usr/local/bin/ $ curl -Lo fission https://github.com/fission/fission/releases/download/0.3.0/fission-cli-osx && chmod +x fission && sudo mv fission /usr/local/bin/
Linux: Linux:
$ curl -Lo fission https://github.com/fission/fission/releases/download/v0.2.1/fission-cli-linux && chmod +x fission && sudo mv fission /usr/local/bin/ $ curl -Lo fission https://github.com/fission/fission/releases/download/0.3.0/fission-cli-linux && chmod +x fission && sudo mv fission /usr/local/bin/
Windows: Windows:
For Windows, you can use the linux binary on WSL. Or you can download this windows executable: https://github.com/fission/fission/releases/download/v0.2.1/fission-cli-windows.exe For Windows, you can use the linux binary on WSL. Or you can download this windows executable: https://github.com/fission/fission/releases/download/0.3.0/fission-cli-windows.exe
2. Set the FISSION_URL and FISSION_ROUTER environment variables. 2. Set the FISSION_URL and FISSION_ROUTER environment variables.
@@ -81,6 +81,15 @@ metadata:
name: fission-function name: fission-function
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
---
apiVersion: v1
kind: Namespace
metadata:
name: {{ .Values.builderNamespace }}
labels:
name: fission-builder
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
--- ---
apiVersion: v1 apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
@@ -153,6 +162,27 @@ roleRef:
name: cluster-admin name: cluster-admin
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: fission-builder
namespace: {{ .Values.builderNamespace }}
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: fission-builder-tpr
subjects:
- kind: ServiceAccount
name: fission-builder
namespace: {{ .Values.builderNamespace }}
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
{{ end }} {{ end }}
--- ---
@@ -240,6 +270,51 @@ spec:
value: "{{ .Values.fetcherImage }}:{{ .Values.fetcherImageTag }}" value: "{{ .Values.fetcherImage }}:{{ .Values.fetcherImageTag }}"
- name: FETCHER_IMAGE_PULL_POLICY - name: FETCHER_IMAGE_PULL_POLICY
value: "{{ .Values.pullPolicy }}" value: "{{ .Values.pullPolicy }}"
- name: RUNTIME_IMAGE_PULL_POLICY
value: "{{ .Values.pullPolicy }}"
serviceAccount: fission-svc
---
apiVersion: v1
kind: Service
metadata:
name: buildermgr
labels:
svc: buildermgr
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8889
selector:
svc: buildermgr
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: buildermgr
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
replicas: 1
template:
metadata:
labels:
svc: buildermgr
spec:
containers:
- name: buildermgr
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
imagePullPolicy: {{ .Values.pullPolicy }}
command: ["/fission-bundle"]
args: ["--builderMgrPort", "8889", "--storageSvcUrl", "http://storagesvc.{{ .Release.Namespace }}", "--envbuilder-namespace", "{{ .Values.builderNamespace }}"]
env:
- name: FETCHER_IMAGE
value: "{{ .Values.fetcherImage }}:{{ .Values.fetcherImageTag }}"
- name: FETCHER_IMAGE_PULL_POLICY
value: "{{ .Values.pullPolicy }}"
serviceAccount: fission-svc serviceAccount: fission-svc
--- ---
@@ -0,0 +1,30 @@
{{ if .Values.analytics }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ template "fullname" . }}
labels:
# The "release" convention makes it easy to tie a release to all of the
# Kubernetes resources that were created as part of that release.
release: {{ .Release.Name }}
# This makes it easy to audit chart usage.
chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app: {{ template "name" . }}
annotations:
# This is what defines this resource as a hook. Without this line, the
# job is considered part of the release.
"helm.sh/hook": post-install
spec:
template:
metadata:
name: {{ template "fullname" . }}
labels:
release: {{ .Release.Name }}
app: {{ template "name" . }}
spec:
restartPolicy: Never
containers:
- name: post-install-job
image: "fission/alpinecurl"
command: ["sh", "-c", "/Usr/bin/curl -m 5 -H \"Content-Type: application/json\" -X POST -d '{\"type\": \"helm-post-install\", \"chartName\": \"{{ .Chart.Name }}\", \"chartName\": \"{{ .Chart.Version }}\"}' https://g.fission.sh/analytics || true"]
{{ end }}
+10 -2
View File
@@ -13,13 +13,13 @@ image: fission/fission-bundle
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
## Fission image version ## Fission image version
imageTag: v0.2.1 imageTag: 0.3.0
## Fission fetcher repository ## Fission fetcher repository
fetcherImage: fission/fetcher fetcherImage: fission/fetcher
## Fission fetcher image version ## Fission fetcher image version
fetcherImageTag: v0.2.1 fetcherImageTag: 0.3.0
## Port at which Fission controller service should be exposed ## Port at which Fission controller service should be exposed
controllerPort: 31313 controllerPort: 31313
@@ -34,6 +34,10 @@ natsStreamingPort: 31316
## the release namespace) ## the release namespace)
functionNamespace: fission-function functionNamespace: fission-function
## Namespace in which to run fission builders (this is different from
## the release namespace)
builderNamespace: fission-builder
## Set up openshift RBAC rule ## Set up openshift RBAC rule
openshift: false openshift: false
@@ -58,3 +62,7 @@ persistence:
# storageClass: # storageClass:
accessMode: ReadWriteOnce accessMode: ReadWriteOnce
size: 8Gi size: 8Gi
## Analytics let us count how many people installed fission. Set to
## false to disable analytics.
analytics: true
+2 -2
View File
@@ -1,6 +1,6 @@
apiVersion: v1 apiVersion: v1
name: fission-core name: fission-core
version: 0.2.1 version: 0.3.0
description: Fission is a fast serverless framework for Kubernetes. description: Fission is a fast serverless framework for Kubernetes.
keywords: keywords:
- fission - fission
@@ -12,4 +12,4 @@ maintainers:
- name: Ta Ching Chen - name: Ta Ching Chen
email: contact@tachingchen.com email: contact@tachingchen.com
engine: gotpl engine: gotpl
appVersion: 0.2.1 appVersion: 0.3.0
+3 -3
View File
@@ -1,13 +1,13 @@
1. Install the client CLI. 1. Install the client CLI.
Mac: Mac:
$ curl -Lo fission https://github.com/fission/fission/releases/download/v0.2.1/fission-cli-osx && chmod +x fission && sudo mv fission /usr/local/bin/ $ curl -Lo fission https://github.com/fission/fission/releases/download/0.3.0/fission-cli-osx && chmod +x fission && sudo mv fission /usr/local/bin/
Linux: Linux:
$ curl -Lo fission https://github.com/fission/fission/releases/download/v0.2.1/fission-cli-linux && chmod +x fission && sudo mv fission /usr/local/bin/ $ curl -Lo fission https://github.com/fission/fission/releases/download/0.3.0/fission-cli-linux && chmod +x fission && sudo mv fission /usr/local/bin/
Windows: Windows:
For Windows, you can use the linux binary on WSL. Or you can download this windows executable: https://github.com/fission/fission/releases/download/v0.2.1/fission-cli-windows.exe For Windows, you can use the linux binary on WSL. Or you can download this windows executable: https://github.com/fission/fission/releases/download/0.3.0/fission-cli-windows.exe
2. Set the FISSION_URL and FISSION_ROUTER environment variables. 2. Set the FISSION_URL and FISSION_ROUTER environment variables.
@@ -81,6 +81,15 @@ metadata:
name: fission-function name: fission-function
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
---
apiVersion: v1
kind: Namespace
metadata:
name: {{ .Values.builderNamespace }}
labels:
name: fission-builder
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
--- ---
apiVersion: v1 apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
@@ -153,6 +162,27 @@ roleRef:
name: cluster-admin name: cluster-admin
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: fission-builder
namespace: {{ .Values.builderNamespace }}
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: fission-builder-tpr
subjects:
- kind: ServiceAccount
name: fission-builder
namespace: {{ .Values.builderNamespace }}
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
{{ end }} {{ end }}
--- ---
@@ -242,6 +272,49 @@ spec:
value: "{{ .Values.pullPolicy }}" value: "{{ .Values.pullPolicy }}"
serviceAccount: fission-svc serviceAccount: fission-svc
---
apiVersion: v1
kind: Service
metadata:
name: buildermgr
labels:
svc: buildermgr
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8889
selector:
svc: buildermgr
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: buildermgr
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
replicas: 1
template:
metadata:
labels:
svc: buildermgr
spec:
containers:
- name: buildermgr
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
imagePullPolicy: {{ .Values.pullPolicy }}
command: ["/fission-bundle"]
args: ["--builderMgrPort", "8889", "--storageSvcUrl", "http://storagesvc.{{ .Release.Namespace }}", "--envbuilder-namespace", "{{ .Values.builderNamespace }}"]
env:
- name: FETCHER_IMAGE
value: "{{ .Values.fetcherImage }}:{{ .Values.fetcherImageTag }}"
- name: FETCHER_IMAGE_PULL_POLICY
value: "{{ .Values.pullPolicy }}"
serviceAccount: fission-svc
--- ---
apiVersion: extensions/v1beta1 apiVersion: extensions/v1beta1
kind: Deployment kind: Deployment
@@ -0,0 +1,30 @@
{{ if .Values.analytics }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ template "fullname" . }}
labels:
# The "release" convention makes it easy to tie a release to all of the
# Kubernetes resources that were created as part of that release.
release: {{ .Release.Name }}
# This makes it easy to audit chart usage.
chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app: {{ template "name" . }}
annotations:
# This is what defines this resource as a hook. Without this line, the
# job is considered part of the release.
"helm.sh/hook": post-install
spec:
template:
metadata:
name: {{ template "fullname" . }}
labels:
release: {{ .Release.Name }}
app: {{ template "name" . }}
spec:
restartPolicy: Never
containers:
- name: post-install-job
image: "fission/alpinecurl"
command: ["sh", "-c", "/usr/bin/curl -m 5 -H \"Content-Type: application/json\" -X POST -d '{\"type\": \"helm-post-install\", \"chartName\": \"{{ .Chart.Name }}\", \"chartName\": \"{{ .Chart.Version }}\"}' https://g.fission.sh/analytics || true"]
{{ end }}
+10 -2
View File
@@ -10,7 +10,7 @@ serviceType: LoadBalancer
image: fission/fission-bundle image: fission/fission-bundle
## Fission image version ## Fission image version
imageTag: v0.2.1 imageTag: 0.3.0
## Image pull policy ## Image pull policy
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
@@ -19,7 +19,7 @@ pullPolicy: IfNotPresent
fetcherImage: fission/fetcher fetcherImage: fission/fetcher
## Fission fetcher image version ## Fission fetcher image version
fetcherImageTag: v0.2.1 fetcherImageTag: 0.3.0
## Port at which Fission controller service should be exposed ## Port at which Fission controller service should be exposed
controllerPort: 31313 controllerPort: 31313
@@ -31,6 +31,10 @@ routerPort: 31314
## the release namespace) ## the release namespace)
functionNamespace: fission-function functionNamespace: fission-function
## Namespace in which to run fission builders (this is different from
## the release namespace)
builderNamespace: fission-builder
## Set up openshift RBAC rule ## Set up openshift RBAC rule
openshift: false openshift: false
@@ -43,3 +47,7 @@ persistence:
# storageClass: # storageClass:
accessMode: ReadWriteOnce accessMode: ReadWriteOnce
size: 8Gi size: 8Gi
## Analytics let us count how many people installed fission. Set to
## false to disable analytics.
analytics: true
+20 -1
View File
@@ -37,6 +37,8 @@ type (
API struct { API struct {
fissionClient *tpr.FissionClient fissionClient *tpr.FissionClient
storageServiceUrl string storageServiceUrl string
builderManagerUrl string
workflowApiUrl string
} }
logDBConfig struct { logDBConfig struct {
@@ -56,6 +58,20 @@ func MakeAPI() (*API, error) {
api.storageServiceUrl = "http://storagesvc" api.storageServiceUrl = "http://storagesvc"
} }
u = os.Getenv("BUILDER_MANAGER_URL")
if len(u) > 0 {
api.builderManagerUrl = strings.TrimSuffix(u, "/")
} else {
api.builderManagerUrl = "http://buildermgr"
}
wfEnv := os.Getenv("WORKFLOW_API_URL")
if len(u) > 0 {
api.workflowApiUrl = strings.TrimSuffix(wfEnv, "/")
} else {
api.workflowApiUrl = "http://workflows-apiserver"
}
return api, err return api, err
} }
@@ -102,7 +118,7 @@ func (api *API) getLogDBConfig(dbType string) logDBConfig {
func (api *API) HomeHandler(w http.ResponseWriter, r *http.Request) { func (api *API) HomeHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
fmt.Fprintf(w, "{\"message\": \"Fission API\", \"version\": \"0.2.1\"}\n") fmt.Fprintf(w, "{\"message\": \"Fission API\", \"version\": \"0.3.0\"}\n")
} }
func (api *API) ApiVersionMismatchHandler(w http.ResponseWriter, r *http.Request) { func (api *API) ApiVersionMismatchHandler(w http.ResponseWriter, r *http.Request) {
@@ -160,6 +176,9 @@ func (api *API) Serve(port int) {
r.HandleFunc("/proxy/{dbType}", api.FunctionLogsApiPost).Methods("POST") r.HandleFunc("/proxy/{dbType}", api.FunctionLogsApiPost).Methods("POST")
r.HandleFunc("/proxy/storage/v1/archive", api.StorageServiceProxy) r.HandleFunc("/proxy/storage/v1/archive", api.StorageServiceProxy)
r.HandleFunc("/proxy/buildermgr/v1/build", api.BuilderManagerBuildProxy)
r.HandleFunc("/proxy/buildermgr/v1/builder", api.BuilderManagerEnvBuilderProxy)
r.HandleFunc("/proxy/workflows-apiserver/{path:.*}", api.WorkflowApiserverProxy)
address := fmt.Sprintf(":%v", port) address := fmt.Sprintf(":%v", port)
+67
View File
@@ -0,0 +1,67 @@
/*
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 (
"fmt"
"log"
"net/http"
"net/http/httputil"
"net/url"
)
func (api *API) BuilderManagerBuildProxy(w http.ResponseWriter, r *http.Request) {
u := api.builderManagerUrl + "/v1/build"
proxy, err := api._getBuilderManagerProxy(u)
if err != nil {
msg := fmt.Sprintf("Failed to establish proxy server: %v", err)
log.Println(msg)
http.Error(w, msg, 500)
return
}
proxy.ServeHTTP(w, r)
}
func (api *API) BuilderManagerEnvBuilderProxy(w http.ResponseWriter, r *http.Request) {
u := api.builderManagerUrl + "/v1/builder"
proxy, err := api._getBuilderManagerProxy(u)
if err != nil {
msg := fmt.Sprintf("Failed to establish proxy server: %v", err)
log.Println(msg)
http.Error(w, msg, 500)
return
}
proxy.ServeHTTP(w, r)
}
func (api *API) _getBuilderManagerProxy(targetUrl string) (*httputil.ReverseProxy, error) {
svcUrl, err := url.Parse(targetUrl)
if err != nil {
return nil, err
}
// set up proxy server director
director := func(req *http.Request) {
// only replace url Scheme and Host to remote server
// and leave query string intact
req.URL.Scheme = svcUrl.Scheme
req.URL.Host = svcUrl.Host
req.URL.Path = svcUrl.Path
}
return &httputil.ReverseProxy{
Director: director,
}, nil
}
+1
View File
@@ -139,6 +139,7 @@ func (a *API) EnvironmentApiUpdate(w http.ResponseWriter, r *http.Request) {
a.respondWithError(w, err) a.respondWithError(w, err)
return return
} }
a.respondWithSuccess(w, resp) a.respondWithSuccess(w, resp)
} }
+31
View File
@@ -0,0 +1,31 @@
package controller
import (
"fmt"
"github.com/gorilla/mux"
"net/http"
"net/http/httputil"
"net/url"
)
func (api *API) WorkflowApiserverProxy(w http.ResponseWriter, r *http.Request) {
u := api.workflowApiUrl
ssUrl, err := url.Parse(u)
if err != nil {
msg := fmt.Sprintf("Error parsing url %v: %v", u, err)
http.Error(w, msg, 500)
return
}
vars := mux.Vars(r)
path := fmt.Sprintf("/%s", vars["path"])
director := func(req *http.Request) {
req.URL.Scheme = ssUrl.Scheme
req.URL.Host = ssUrl.Host
req.URL.Path = path
}
proxy := &httputil.ReverseProxy{
Director: director,
}
proxy.ServeHTTP(w, r)
}
+9
View File
@@ -0,0 +1,9 @@
FROM microsoft/dotnet:2.0-runtime
WORKDIR /fission-workdir
COPY out .
EXPOSE 8888
ENTRYPOINT ["dotnet"]
CMD ["fission-dotnet20.dll"]
+82
View File
@@ -0,0 +1,82 @@
using Fission.DotNetCore.Compiler;
using Fission.DotNetCore.Api;
using System.Collections.Generic;
using Nancy;
using System.IO;
using System;
namespace Fission.DotNetCore
{
public class ExecutorModule : NancyModule
{
#if DEBUG
private const string CODE_PATH = "/tmp/func.cs";
#else
private const string CODE_PATH = "/userfunc/user";
#endif
private static Function _userFunc;
private static Logger _logger = new Logger();
public ExecutorModule()
{
Post("/specialize", args => Specialize());
Get("/", _ => Run());
Post("/", _ => Run());
Put("/", _ => Run());
Head("/", _ => Run());
Options("/", _ => Run());
Delete("/", _ => Run());
}
private object Specialize()
{
var errors = new List<string>();
if (File.Exists(CODE_PATH))
{
var code = File.ReadAllText(CODE_PATH);
_userFunc = FissionCompiler.Compile(code, out errors);
if (_userFunc == null)
{
var errstr = string.Join(Environment.NewLine, errors);
_logger.WriteError(errstr);
var response = (Response)errstr;
response.StatusCode = HttpStatusCode.InternalServerError;
return response;
}
return null;
}
else
{
var errstr = $"Unable to locate code at '{CODE_PATH}'";
_logger.WriteError(errstr);
var response = (Response)errstr;
response.StatusCode = HttpStatusCode.InternalServerError;
return response;
}
}
private object Run()
{
if (_userFunc == null)
{
var response = (Response)"Generic container: no requests supported";
response.StatusCode = HttpStatusCode.InternalServerError;
return response;
}
try
{
return _userFunc.Invoke(FissionContext.Build(Request, new Logger()));
}
catch (Exception e)
{
_logger.WriteError(e.ToString());
var response = (Response)e.Message;
response.StatusCode = HttpStatusCode.BadRequest;
return response;
}
}
}
}
+75
View File
@@ -0,0 +1,75 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
namespace Fission.DotNetCore.Compiler
{
//adapted from this article http://www.tugberkugurlu.com/archive/compiling-c-sharp-code-into-memory-and-executing-it-with-roslyn
class FissionCompiler
{
public static Function Compile(string code, out List<string> errors)
{
errors = new List<string>();
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
string assemblyName = Path.GetRandomFileName();
var coreDir = Directory.GetParent(typeof(Enumerable).GetTypeInfo().Assembly.Location);
List<MetadataReference> references = new List<MetadataReference>
{
MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar + "mscorlib.dll"),
MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(Assembly.GetEntryAssembly().Location),
MetadataReference.CreateFromFile(typeof(System.Runtime.Serialization.Json.DataContractJsonSerializer).GetTypeInfo().Assembly.Location)
};
foreach (var referencedAssembly in Assembly.GetEntryAssembly().GetReferencedAssemblies())
{
var assembly = Assembly.Load(referencedAssembly);
references.Add(MetadataReference.CreateFromFile(assembly.Location));
}
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(
OutputKind.DynamicallyLinkedLibrary,
optimizationLevel: OptimizationLevel.Release));
using (var ms = new MemoryStream())
{
EmitResult result = compilation.Emit(ms);
if (!result.Success)
{
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error).ToList();
foreach (Diagnostic diagnostic in failures)
{
errors.Add($"{diagnostic.Id}: {diagnostic.GetMessage()}");
}
}
else
{
ms.Seek(0, SeekOrigin.Begin);
Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(ms);
var type = assembly.GetType("FissionFunction");
var info = type.GetMember("Execute").First() as MethodInfo;
return new Function(assembly, type, info);
}
}
return null;
}
}
}
+114
View File
@@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Nancy;
namespace Fission.DotNetCore.Api
{
public class FissionContext
{
public FissionContext(Dictionary<string, object> args, Logger logger, FissionHttpRequest request)
{
if (args == null) throw new ArgumentNullException(nameof(args));
if (logger == null) throw new ArgumentNullException(nameof(logger));
if (request == null) throw new ArgumentNullException(nameof(request));
Arguments = args;
Logger = logger;
Request = request;
}
public Dictionary<string, object> Arguments { get; private set; }
public FissionHttpRequest Request { get; private set; }
public Logger Logger { get; private set; }
public static FissionContext Build(Request request, Logger logger)
{
return new FissionContext(((DynamicDictionary)request.Query).ToDictionary(),
logger,
new FissionHttpRequest(request));
}
}
public class Logger
{
public void Write(Severity severity, string format, params object[] args)
{
Console.WriteLine($"{DateTime.Now.ToString("MM/dd/yy H:mm:ss zzz")} {severity}: " + format, args);
}
public void WriteInfo(string format, params object[] args)
{
Write(Severity.Info, format, args);
}
public void WriteWarning(string format, params object[] args)
{
Write(Severity.Warning, format, args);
}
public void WriteError(string format, params object[] args)
{
Write(Severity.Error, format, args);
}
public void WriteCritical(string format, params object[] args)
{
Write(Severity.Critical, format, args);
}
public void WriteVerbose(string format, params object[] args)
{
Write(Severity.Verbose, format, args);
}
}
public enum Severity
{
Info,
Warning,
Error,
Critical,
Verbose
}
public class FissionHttpRequest
{
private readonly Request _request;
internal FissionHttpRequest(Request request)
{
if (request == null) throw new ArgumentNullException(nameof(request));
_request = request;
}
public Stream Body { get { return _request.Body; } }
public string BodyAsString()
{
int length = (int)_request.Body.Length;
byte[] data = new byte[length];
_request.Body.Read(data, 0, length);
return Encoding.UTF8.GetString(data);
}
public Dictionary<string, IEnumerable<string>> Headers
{
get
{
var headers = new Dictionary<string, IEnumerable<string>>();
foreach (var kv in _request.Headers)
{
headers.Add(kv.Key, kv.Value);
}
return headers;
}
}
public X509Certificate Certificate { get { return _request.ClientCertificate; } }
public string Url { get { return _request.Url.ToString(); } }
public string Method { get { return _request.Method; } }
}
}
+28
View File
@@ -0,0 +1,28 @@
using System;
using System.Reflection;
using Fission.DotNetCore.Api;
namespace Fission.DotNetCore.Compiler
{
class Function
{
private readonly Assembly _assembly;
private readonly Type _type;
private readonly MethodInfo _info;
public Function(Assembly assembly, Type type, MethodInfo info)
{
if (info == null) throw new ArgumentNullException(nameof(info));
if (assembly == null) throw new ArgumentNullException(nameof(assembly));
if (type == null) throw new ArgumentNullException(nameof(type));
_assembly = assembly;
_type = type;
_info = info;
}
public object Invoke(FissionContext context)
{
return _info.Invoke(_assembly.CreateInstance(_type.FullName), new[] { context });
}
}
}
+21
View File
@@ -0,0 +1,21 @@
using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Nancy.Owin;
namespace Fission.DotNetCore
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseKestrel()
.UseUrls("http://*:8888")
.Configure(app => app.UseOwin(x => x.UseNancy()))
.Build();
host.Run();
}
}
}
+306
View File
@@ -0,0 +1,306 @@
# Fission: dotnet 2.0 C# Environment
This is a simple dotnet core 2.0 C# environment for Fission.
It's a Docker image containing the dotnet 2.0.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 core 2.0.0
installed or else you will have to map the source directory into a
container containing the dotnet core 2.0.0 environment.
### Locally installed Dotnet core 2.0.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 fission-dotnet20.csproj
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/dotnet20-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/dotnet20-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/dotnet20-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/dotnet20-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/dotnet20-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
```
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
THIS_DIR=$(realpath $(dirname $0))
docker run -v $THIS_DIR:/proj microsoft/dotnet:2.0.0-sdk sh -c "cd /proj && ./project-build.sh"
+25
View File
@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.12
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "fission-dotnet20", "dotnet20\fission-dotnet20.csproj", "{3F044DE1-74E5-48F7-8D23-233C24AAA45C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3F044DE1-74E5-48F7-8D23-233C24AAA45C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3F044DE1-74E5-48F7-8D23-233C24AAA45C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F044DE1-74E5-48F7-8D23-233C24AAA45C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F044DE1-74E5-48F7-8D23-233C24AAA45C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {272000F7-BBEC-4B61-8865-783EF7E5CEE9}
EndGlobalSection
EndGlobal
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Owin" Version="2.0.0" />
<PackageReference Include="Nancy" Version="2.0.0-clinteastwood" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="2.3.2" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
<PackageReference Include="System.Runtime.Serialization.Json" Version="4.3.0" />
</ItemGroup>
</Project>
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
dotnet restore fission-dotnet20.csproj
dotnet publish fission-dotnet20.csproj -c Release -o out
+47 -2
View File
@@ -3,6 +3,8 @@ package client
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"io/ioutil"
"log"
"net/http" "net/http"
//"time" //"time"
@@ -11,7 +13,19 @@ import (
//"github.com/fission/fission/router" //"github.com/fission/fission/router"
) )
func DoFetchRequest(fetcherUrl string, fr *fetcher.FetchRequest) error { type (
Client struct {
url string
}
)
func MakeClient(fetcherUrl string) *Client {
return &Client{
url: fetcherUrl,
}
}
func (c *Client) Fetch(fr *fetcher.FetchRequest) error {
body, err := json.Marshal(fr) body, err := json.Marshal(fr)
if err != nil { if err != nil {
return err return err
@@ -21,7 +35,7 @@ func DoFetchRequest(fetcherUrl string, fr *fetcher.FetchRequest) error {
// Transport: router.MakeRetryingRoundTripper(10, 50*time.Millisecond), // Transport: router.MakeRetryingRoundTripper(10, 50*time.Millisecond),
// } // }
resp, err := http.Post(fetcherUrl, "application/json", bytes.NewReader(body)) resp, err := http.Post(c.url, "application/json", bytes.NewReader(body))
if err != nil { if err != nil {
return err return err
} }
@@ -33,3 +47,34 @@ func DoFetchRequest(fetcherUrl string, fr *fetcher.FetchRequest) error {
return nil return nil
} }
func (c *Client) Upload(fr *fetcher.UploadRequest) (*fetcher.UploadResponse, error) {
body, err := json.Marshal(fr)
if err != nil {
return nil, err
}
resp, err := http.Post(c.url+"/upload", "application/json", bytes.NewReader(body))
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fission.MakeErrorFromHTTP(resp)
}
rBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
log.Printf("Received upload response: %v", string(rBody))
uploadReq := fetcher.UploadResponse{}
err = json.Unmarshal([]byte(rBody), &uploadReq)
if err != nil {
return nil, err
}
return &uploadReq, nil
}
+2 -1
View File
@@ -21,6 +21,7 @@ func main() {
} }
fetcher := fetcher.MakeFetcher(dir) fetcher := fetcher.MakeFetcher(dir)
mux := http.NewServeMux() mux := http.NewServeMux()
mux.HandleFunc("/", fetcher.Handler) mux.HandleFunc("/", fetcher.FetchHandler)
mux.HandleFunc("/upload", fetcher.UploadHandler)
http.ListenAndServe(":8000", mux) http.ListenAndServe(":8000", mux)
} }
+135 -26
View File
@@ -20,6 +20,7 @@ import (
"k8s.io/client-go/1.5/pkg/api" "k8s.io/client-go/1.5/pkg/api"
"github.com/fission/fission" "github.com/fission/fission"
storageSvcClient "github.com/fission/fission/storagesvc/client"
"github.com/fission/fission/tpr" "github.com/fission/fission/tpr"
) )
@@ -28,12 +29,26 @@ type (
FetchRequest struct { FetchRequest struct {
FetchType FetchRequestType `json:"fetchType"` FetchType FetchRequestType `json:"fetchType"`
Function api.ObjectMeta `json:"function"` Package api.ObjectMeta `json:"package"`
Url string `json:"url"` Url string `json:"url"`
StorageSvcUrl string `json:"storagesvcurl"` StorageSvcUrl string `json:"storagesvcurl"`
Filename string `json:"filename"` Filename string `json:"filename"`
} }
// UploadRequest send from builder manager describes which
// deployment package should be upload to storage service.
UploadRequest struct {
Filename string `json:"filename"`
StorageSvcUrl string `json:"storagesvcurl"`
}
// UploadResponse defines the download url of an archive and
// its checksum.
UploadResponse struct {
ArchiveDownloadUrl string `json:"archiveDownloadUrl"`
Checksum fission.Checksum `json:"checksum"`
}
Fetcher struct { Fetcher struct {
sharedVolumePath string sharedVolumePath string
fissionClient *tpr.FissionClient fissionClient *tpr.FissionClient
@@ -79,33 +94,44 @@ func downloadUrl(url string, localPath string) error {
return nil return nil
} }
func verifyChecksum(path string, checksum *fission.Checksum) error { func getChecksum(path string) (*fission.Checksum, error) {
if checksum.Type != fission.ChecksumTypeSHA256 {
return fission.MakeError(fission.ErrorInvalidArgument, "Unsupported checksum type")
}
f, err := os.Open(path) f, err := os.Open(path)
if err != nil { if err != nil {
return err return nil, err
} }
defer f.Close() defer f.Close()
hasher := sha256.New() hasher := sha256.New()
_, err = io.Copy(hasher, f) _, err = io.Copy(hasher, f)
if err != nil { if err != nil {
return err return nil, err
} }
c := hex.EncodeToString(hasher.Sum(nil)) c := hex.EncodeToString(hasher.Sum(nil))
if c != checksum.Sum {
return &fission.Checksum{
Type: fission.ChecksumTypeSHA256,
Sum: c,
}, nil
}
func verifyChecksum(path string, checksum *fission.Checksum) error {
if checksum.Type != fission.ChecksumTypeSHA256 {
return fission.MakeError(fission.ErrorInvalidArgument, "Unsupported checksum type")
}
c, err := getChecksum(path)
if err != nil {
return err
}
if c.Sum != checksum.Sum {
return fission.MakeError(fission.ErrorChecksumFail, "Checksum validation failed") return fission.MakeError(fission.ErrorChecksumFail, "Checksum validation failed")
} }
return nil return nil
} }
func (fetcher *Fetcher) Handler(w http.ResponseWriter, r *http.Request) { func (fetcher *Fetcher) FetchHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" { if r.Method != "POST" {
http.Error(w, "", 405) http.Error(w, "only POST is supported on this endpoint", 405)
return return
} }
@@ -129,11 +155,13 @@ func (fetcher *Fetcher) Handler(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), 400) http.Error(w, err.Error(), 400)
return return
} }
log.Printf("fetcher received request: %v", req) log.Printf("fetcher received fetch request: %v", req)
tmpFile := req.Filename + ".tmp" tmpFile := req.Filename + ".tmp"
tmpPath := filepath.Join(fetcher.sharedVolumePath, tmpFile) tmpPath := filepath.Join(fetcher.sharedVolumePath, tmpFile)
log.Printf("Start downloading...")
if req.FetchType == FETCH_URL { if req.FetchType == FETCH_URL {
// fetch the file and save it to the tmp path // fetch the file and save it to the tmp path
err := downloadUrl(req.Url, tmpPath) err := downloadUrl(req.Url, tmpPath)
@@ -144,19 +172,8 @@ func (fetcher *Fetcher) Handler(w http.ResponseWriter, r *http.Request) {
return return
} }
} else { } else {
// get function object
fn, err := fetcher.fissionClient.Functions(req.Function.Namespace).Get(req.Function.Name)
if err != nil {
e := fmt.Sprintf("Failed to get function: %v", err)
log.Printf(e)
http.Error(w, e, 500)
return
}
// get pkg // get pkg
var pkg *tpr.Package pkg, err := fetcher.fissionClient.Packages(req.Package.Namespace).Get(req.Package.Name)
pkg, err = fetcher.fissionClient.
Packages(fn.Spec.Package.PackageRef.Namespace).Get(fn.Spec.Package.PackageRef.Name)
if err != nil { if err != nil {
e := fmt.Sprintf("Failed to get package: %v", err) e := fmt.Sprintf("Failed to get package: %v", err)
log.Printf(e) log.Printf(e)
@@ -200,7 +217,6 @@ func (fetcher *Fetcher) Handler(w http.ResponseWriter, r *http.Request) {
return return
} }
} }
} }
// check file type here, if the file is a zip file unarchive it. // check file type here, if the file is a zip file unarchive it.
@@ -229,6 +245,85 @@ func (fetcher *Fetcher) Handler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
func (fetcher *Fetcher) UploadHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "only POST is supported on this endpoint", 405)
return
}
startTime := time.Now()
defer func() {
elapsed := time.Now().Sub(startTime)
log.Printf("elapsed time in upload request = %v", elapsed)
}()
// parse request
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Error reading request body")
http.Error(w, err.Error(), 500)
return
}
var req UploadRequest
err = json.Unmarshal(body, &req)
if err != nil {
log.Printf("Error reading request body: %v", err)
http.Error(w, err.Error(), 400)
return
}
log.Printf("fetcher received upload request: %v", req)
zipFilename := req.Filename + ".zip"
srcFilepath := filepath.Join(fetcher.sharedVolumePath, req.Filename)
dstFilepath := filepath.Join(fetcher.sharedVolumePath, zipFilename)
err = fetcher.archive(srcFilepath, dstFilepath)
if err != nil {
e := fmt.Sprintf("Error archiving zip file: %v", err)
log.Println(e)
http.Error(w, e, 500)
return
}
log.Println("Start uploading...")
ssClient := storageSvcClient.MakeClient(req.StorageSvcUrl)
fileID, err := ssClient.Upload(dstFilepath, nil)
if err != nil {
e := fmt.Sprintf("Error uploading zip file: %v", err)
log.Println(e)
http.Error(w, e, 500)
return
}
sum, err := getChecksum(dstFilepath)
if err != nil {
e := fmt.Sprintf("Error calculating checksum of zip file: %v", err)
log.Println(e)
http.Error(w, e, 500)
return
}
resp := UploadResponse{
ArchiveDownloadUrl: ssClient.GetUrl(fileID),
Checksum: *sum,
}
rBody, err := json.Marshal(resp)
if err != nil {
e := fmt.Sprintf("Error encoding upload response: %v", err)
log.Println(e)
http.Error(w, e, 500)
return
}
log.Println("Completed upload request")
w.Header().Add("Content-Type", "application/json")
w.Write(rBody)
w.WriteHeader(http.StatusOK)
}
func (fetcher *Fetcher) rename(src string, dst string) error { func (fetcher *Fetcher) rename(src string, dst string) error {
err := os.Rename(src, dst) err := os.Rename(src, dst)
if err != nil { if err != nil {
@@ -239,7 +334,21 @@ func (fetcher *Fetcher) rename(src string, dst string) error {
// archive is a function that zips directory into a zip file // archive is a function that zips directory into a zip file
func (fetcher *Fetcher) archive(src string, dst string) error { func (fetcher *Fetcher) archive(src string, dst string) error {
return archiver.Zip.Make(dst, []string{src}) var files []string
target, err := os.Stat(src)
if err != nil {
return errors.New(fmt.Sprintf("Failed to zip file: %v", err))
}
if target.IsDir() {
// list all
fs, _ := ioutil.ReadDir(src)
for _, f := range fs {
files = append(files, filepath.Join(src, f.Name()))
}
} else {
files = append(files, src)
}
return archiver.Zip.Make(dst, files)
} }
// unarchive is a function that unzips a zip file to destination // unarchive is a function that unzips a zip file to destination
+1 -1
View File
@@ -1,6 +1,6 @@
# A docker image for the func container. # A docker image for the func container.
FROM node:7-onbuild FROM node:8-onbuild
ADD server.js /usr/src/app/server.js ADD server.js /usr/src/app/server.js
+5 -5
View File
@@ -13,12 +13,12 @@
"node": ">=7.6.0" "node": ">=7.6.0"
}, },
"dependencies": { "dependencies": {
"body-parser": "", "body-parser": "*",
"co": "~4.6.0", "co": "~4.6.0",
"express": "", "express": "*",
"minimist": "", "minimist": "*",
"morgan": "", "morgan": "*",
"mz": "~2.1.0", "mz": "~2.7.0",
"request": "^2.81.0", "request": "^2.81.0",
"request-promise-native": "^1.0.3", "request-promise-native": "^1.0.3",
"underscore": ">=1.8.3" "underscore": ">=1.8.3"
+1
View File
@@ -66,6 +66,7 @@ app.use(morgan('combined'))
app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json()); app.use(bodyParser.json());
app.use(bodyParser.raw()); app.use(bodyParser.raw());
app.use(bodyParser.text({ type : "text/*" }));
app.post('/specialize', specialize); app.post('/specialize', specialize);
+2 -2
View File
@@ -6,6 +6,6 @@ RUN pip3 install --upgrade pip
RUN rm -r /root/.cache RUN rm -r /root/.cache
ADD defaultBuildCmd /usr/local/bin/build ADD defaultBuildCmd /usr/local/bin/build
ADD builder / ADD builder /builder
EXPOSE 8000 EXPOSE 8001
-11
View File
@@ -1,11 +0,0 @@
#!/bin/sh
set -e
builderDir=${GOPATH}/src/github.com/fission/fission/builder/cmd
pushd ${builderDir}
GOOS=linux GOARCH=386 go build -o builder .
popd
cp ${builderDir}/builder .
docker build -t python-builder .
docker tag python-builder fission/python-builder:$tag
docker push fission/python-builder:$tag
+16 -2
View File
@@ -3,21 +3,35 @@
import logging import logging
import sys import sys
import imp import imp
import os
from flask import Flask, request, abort, g from flask import Flask, request, abort, g
app = Flask(__name__) app = Flask(__name__)
codepath = '/userfunc/user'
userfunc = None userfunc = None
@app.route('/specialize', methods=['POST']) @app.route('/specialize', methods=['POST'])
def load(): def load():
global userfunc global userfunc
# load user function from codepath
codepath = '/userfunc/user'
userfunc = (imp.load_source('user', codepath)).main userfunc = (imp.load_source('user', codepath)).main
return "" return ""
@app.route('/v2/specialize', methods=['POST'])
def loadv2():
global userfunc
body = request.get_json()
filepath = body['filepath']
functionName = body['functionName']
# add filepath into syspath for module import
sys.path.append(filepath)
fn, path, desc = imp.find_module('user', [filepath])
mod = imp.load_module('user', fn, path, desc)
userfunc = getattr(mod, functionName)
return ""
@app.route('/', methods=['GET', 'POST', 'PUT', 'HEAD', 'OPTIONS', 'DELETE']) @app.route('/', methods=['GET', 'POST', 'PUT', 'HEAD', 'OPTIONS', 'DELETE'])
def f(): def f():
if userfunc == None: if userfunc == None:
+11
View File
@@ -0,0 +1,11 @@
using System;
using Fission.DotNetCore.Api;
public class FissionFunction
{
public string Execute(FissionContext context){
var x = Convert.ToInt32(context.Arguments["x"]);
var y = Convert.ToInt32(context.Arguments["y"]);
return (x+y).ToString();
}
+10
View File
@@ -0,0 +1,10 @@
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"];
}
}
+9
View File
@@ -0,0 +1,9 @@
using Fission.DotNetCore.Api;
public class FissionFunction
{
public string Execute(FissionContext context)
{
return "Hello World!";
}
}
+24
View File
@@ -0,0 +1,24 @@
using System.IO;
using System.Runtime.Serialization.Json;
using Fission.DotNetCore.Api;
public class FissionFunction
{
public string Execute(FissionContext context)
{
var person = Person.Deserialize(context.Request.Body);
return $"Hello, my name is {person.Name} and I am {person.Age} years old.";
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public static Person Deserialize(Stream json)
{
var serializer = new DataContractJsonSerializer(typeof(Person));
return (Person)serializer.ReadObject(json);
}
}
+17
View File
@@ -0,0 +1,17 @@
using System;
using Fission.DotNetCore.Api;
public class FissionFunction
{
public string Execute(FissionContext context){
var buffer = new System.Text.StringBuilder();
foreach(var header in context.Request.Headers){
buffer.AppendLine(header.Key);
foreach(var item in header.Value){
buffer.AppendLine($"\t{item}");
}
}
buffer.AppendLine($"Url: {context.Request.Url}, method: {context.Request.Method}");
return buffer.ToString();
}
}
+2
View File
@@ -0,0 +1,2 @@
#!/bin/sh
pip3 install -r ${SRC_PKG}/requirements.txt -t ${SRC_PKG} && cp -r ${SRC_PKG} ${DEPLOY_PKG}
@@ -0,0 +1 @@
pyyaml
+12
View File
@@ -0,0 +1,12 @@
import sys
import yaml
document = """
a: 1
b:
c: 3
d: 4
"""
def main():
return yaml.dump(yaml.load(document))
+1 -1
View File
@@ -1,2 +1,2 @@
#!/bin/sh #!/bin/sh
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
+31 -13
View File
@@ -6,6 +6,7 @@ import (
"strconv" "strconv"
"github.com/docopt/docopt-go" "github.com/docopt/docopt-go"
"github.com/fission/fission/buildermgr"
"github.com/fission/fission/controller" "github.com/fission/fission/controller"
"github.com/fission/fission/kubewatcher" "github.com/fission/fission/kubewatcher"
"github.com/fission/fission/logger" "github.com/fission/fission/logger"
@@ -68,6 +69,13 @@ func runStorageSvc(port int, filePath string) {
filePath, subdir, port) filePath, subdir, port)
} }
func runBuilderMgr(port int, storageSvcUrl string, envBuilderNamespace string) {
err := buildermgr.Start(port, storageSvcUrl, envBuilderNamespace)
if err != nil {
log.Fatalf("Error starting buildermgr: %v", err)
}
}
func getPort(portArg interface{}) int { func getPort(portArg interface{}) int {
portArgStr := portArg.(string) portArgStr := portArg.(string)
port, err := strconv.Atoi(portArgStr) port, err := strconv.Atoi(portArgStr)
@@ -113,23 +121,26 @@ Usage:
fission-bundle --poolmgrPort=<port> [--namespace=<namespace>] [--fission-namespace=<namespace>] fission-bundle --poolmgrPort=<port> [--namespace=<namespace>] [--fission-namespace=<namespace>]
fission-bundle --kubewatcher [--routerUrl=<url>] fission-bundle --kubewatcher [--routerUrl=<url>]
fission-bundle --storageServicePort=<port> --filePath=<filePath> fission-bundle --storageServicePort=<port> --filePath=<filePath>
fission-bundle --builderMgrPort=<port> [--storageSvcUrl=<url>] [--envbuilder-namespace=<namespace>]
fission-bundle --logger fission-bundle --logger
fission-bundle --timer [--routerUrl=<url>] fission-bundle --timer [--routerUrl=<url>]
fission-bundle --mqt [--routerUrl=<url>] fission-bundle --mqt [--routerUrl=<url>]
Options: Options:
--controllerPort=<port> Port that the controller should listen on. --controllerPort=<port> Port that the controller should listen on.
--routerPort=<port> Port that the router should listen on. --routerPort=<port> Port that the router should listen on.
--poolmgrPort=<port> Port that the poolmgr should listen on. --poolmgrPort=<port> Port that the poolmgr should listen on.
--storageServicePort=<port> Port that the storage service should listen on. --storageServicePort=<port> Port that the storage service should listen on.
--poolmgrUrl=<url> Poolmgr URL. Not required if --poolmgrPort is specified. --builderMgrPort=<port> Port that the buildermgr should listen on.
--routerUrl=<url> Router URL. --poolmgrUrl=<url> Poolmgr URL. Not required if --poolmgrPort is specified.
--etcdUrl=<etcdUrl> Etcd URL. --routerUrl=<url> Router URL.
--filePath=<filePath> Directory to store functions in. --etcdUrl=<etcdUrl> Etcd URL.
--namespace=<namespace> Kubernetes namespace in which to run function containers. Defaults to 'fission-function'. --storageSvcUrl=<url> StorageService URL.
--kubewatcher Start Kubernetes events watcher. --filePath=<filePath> Directory to store functions in.
--logger Start logger. --namespace=<namespace> Kubernetes namespace in which to run function containers. Defaults to 'fission-function'.
--timer Start Timer. --kubewatcher Start Kubernetes events watcher.
--mqt Start message queue trigger. --logger Start logger.
--timer Start Timer.
--mqt Start message queue trigger.
` `
arguments, err := docopt.Parse(usage, nil, true, "fission-bundle", false) arguments, err := docopt.Parse(usage, nil, true, "fission-bundle", false)
if err != nil { if err != nil {
@@ -138,9 +149,11 @@ Options:
functionNs := getStringArgWithDefault(arguments["--namespace"], "fission-function") functionNs := getStringArgWithDefault(arguments["--namespace"], "fission-function")
fissionNs := getStringArgWithDefault(arguments["--fission-namespace"], "fission") fissionNs := getStringArgWithDefault(arguments["--fission-namespace"], "fission")
envBuilderNs := getStringArgWithDefault(arguments["--envbuilder-namespace"], "fission-builder")
poolmgrUrl := getStringArgWithDefault(arguments["--poolmgrUrl"], "http://poolmgr.fission") poolmgrUrl := getStringArgWithDefault(arguments["--poolmgrUrl"], "http://poolmgr.fission")
routerUrl := getStringArgWithDefault(arguments["--routerUrl"], "http://router.fission") routerUrl := getStringArgWithDefault(arguments["--routerUrl"], "http://router.fission")
storageSvcUrl := getStringArgWithDefault(arguments["--storageSvcUrl"], "http://storagesvc.fission")
if arguments["--controllerPort"] != nil { if arguments["--controllerPort"] != nil {
port := getPort(arguments["--controllerPort"]) port := getPort(arguments["--controllerPort"])
@@ -179,5 +192,10 @@ Options:
runStorageSvc(port, filePath) runStorageSvc(port, filePath)
} }
if arguments["--builderMgrPort"] != nil {
port := getPort(arguments["--builderMgrPort"])
runBuilderMgr(port, storageSvcUrl, envBuilderNs)
}
select {} select {}
} }
-69
View File
@@ -1,69 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: router
namespace: fission
labels:
svc: router
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8888
nodePort: 31314
selector:
svc: router
---
apiVersion: v1
kind: Service
metadata:
name: controller
namespace: fission
labels:
svc: controller
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8888
nodePort: 31313
selector:
svc: controller
---
# If you want to switch to external db service, please checkout
# sample Kubernetes Service config in fission-logger.yaml.
apiVersion: v1
kind: Service
metadata:
name: influxdb
namespace: fission
labels:
svc: influxdb
spec:
type: LoadBalancer
ports:
- port: 8086
targetPort: 8086
nodePort: 31315
selector:
svc: influxdb
---
apiVersion: v1
kind: Service
metadata:
name: nats-streaming
namespace: fission
labels:
svc: nats-streaming
spec:
type: LoadBalancer
ports:
- port: 4222
targetPort: 4222
nodePort: 31316
selector:
svc: nats-streaming
-122
View File
@@ -1,122 +0,0 @@
# Sample Kubernetes Service config for external db service.
#
# apiVersion: v1
# kind: Service
# metadata:
# name: influxdb
# namespace: fission
# spec:
# ports:
# - name: "8086"
# port: 8086
# targetPort: 8086
# protocol: TCP
# ---
# apiVersion: v1
# kind: Endpoints
# metadata:
# name: influxdb
# namespace: fission
# subsets:
# - addresses:
# - ip: replace.influxdb.host.here
# ports:
# - name: "8086"
# port: 8086
# protocol: TCP
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: influxdb
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: influxdb
spec:
containers:
- name: influxdb
image: tutum/influxdb
env:
- name: PRE_CREATE_DB
value: fissionFunctionLog
# Create a random username/password for InfluxDB here, and
# repeat it in the fluentd spec below.
- name: ADMIN_USER
value: ""
- name: INFLUXDB_INIT_PWD
value: ""
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: logger
namespace: fission
spec:
template:
metadata:
labels:
svc: logger
spec:
containers:
- name: logger
image: fission/fission-bundle
imagePullPolicy: IfNotPresent
command: ["/fission-bundle"]
args: ["--logger"]
volumeMounts:
- name: container-log
mountPath: /var/log/containers
readOnly: true
- name: docker-log
mountPath: /var/lib/docker/containers
readOnly: true
- name: fission-log
mountPath: /var/log/fission
readOnly: false
ports:
- containerPort: 1234
hostPort: 1234
protocol: TCP
- name: fluentd
image: fission/fluentd
imagePullPolicy: IfNotPresent
env:
- name: INFLUXDB_ADDRESS
value: influxdb
- name: INFLUXDB_PORT
value: "8086"
- name: INFLUXDB_DBNAME
value: "fissionFunctionLog"
# Username/password for fluentd to push logs to
# influxdb. This should match the influxdb
# username/passowrd defined above.
- name: INFLUXDB_USERNAME
value: ""
- name: INFLUXDB_PASSWD
value: ""
volumeMounts:
- name: container-log
mountPath: /var/log/containers
readOnly: true
- name: docker-log
mountPath: /var/lib/docker/containers
readOnly: true
- name: fission-log
mountPath: /var/log/fission
readOnly: false
volumes:
- name: container-log
hostPath:
path: /var/log/containers
- name: docker-log
hostPath:
path: /var/lib/docker/containers
- name: fission-log
hostPath:
path: /var/log/fission
-60
View File
@@ -1,60 +0,0 @@
# To enable nats authentication, please follow the instruction described in
# http://nats.io/documentation/server/gnatsd-authentication/.
# And dont forget to change the MESSAGE_QUEUE_URL in mqtrigger deployment.
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: mqtrigger
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: mqtrigger
spec:
containers:
- name: mqtrigger
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--mqt"]
env:
- name: MESSAGE_QUEUE_TYPE
value: nats-streaming
- name: MESSAGE_QUEUE_URL
value: nats://nats-streaming:4222
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
svc: nats-streaming
name: nats-streaming
namespace: fission
spec:
replicas: 1
selector:
matchLabels:
svc: nats-streaming
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
svc: nats-streaming
spec:
containers:
- name: nats-streaming
image: nats-streaming
args: ["--cluster_id", "fissionMQTrigger"]
ports:
- containerPort: 4222
hostPort: 4222
protocol: TCP
-69
View File
@@ -1,69 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: router
namespace: fission
labels:
svc: router
spec:
type: NodePort
ports:
- port: 80
targetPort: 8888
nodePort: 31314
selector:
svc: router
---
apiVersion: v1
kind: Service
metadata:
name: controller
namespace: fission
labels:
svc: controller
spec:
type: NodePort
ports:
- port: 80
targetPort: 8888
nodePort: 31313
selector:
svc: controller
---
# If you want to switch to external db service, please checkout
# sample Kubernetes Service config in fission-logger.yaml.
apiVersion: v1
kind: Service
metadata:
name: influxdb
namespace: fission
labels:
svc: influxdb
spec:
type: NodePort
ports:
- port: 8086
targetPort: 8086
nodePort: 31315
selector:
svc: influxdb
---
apiVersion: v1
kind: Service
metadata:
name: nats-streaming
namespace: fission
labels:
svc: nats-streaming
spec:
type: NodePort
ports:
- port: 4222
targetPort: 4222
nodePort: 31316
selector:
svc: nats-streaming
-202
View File
@@ -1,202 +0,0 @@
---
apiVersion: v1
kind: ProjectRequest
metadata:
name: fission
labels:
name: fission
---
apiVersion: v1
kind: ProjectRequest
metadata:
name: fission-function
labels:
name: fission-function
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: poolmgr
namespace: fission
---
apiVersion: v1
kind: ClusterRole
metadata:
name: fission:poolmgr
rules:
- apiGroups:
- extensions
attributeRestrictions: null
resources:
- deployments
verbs:
- create
- get
- list
- update
- watch
- apiGroups:
- ""
attributeRestrictions: null
resources:
- pods
verbs:
- get
- list
- update
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: controller
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: controller
spec:
volumes:
- name: controller-storage
emptyDir: {}
containers:
- name: controller
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--controllerPort", "8888", "--filepath", "/filestore"]
volumeMounts:
- name: controller-storage
mountPath: /filestore
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: router
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: router
spec:
containers:
- name: router
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--routerPort", "8888"]
---
apiVersion: v1
kind: Service
metadata:
name: poolmgr
namespace: fission
labels:
svc: poolmgr
spec:
ports:
- port: 80
targetPort: 8888
selector:
svc: poolmgr
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: poolmgr
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: poolmgr
spec:
containers:
- name: poolmgr
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--poolmgrPort", "8888"]
serviceAccount: poolmgr
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: kubewatcher
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: kubewatcher
spec:
containers:
- name: kubewatcher
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--kubewatcher"]
---
apiVersion: v1
kind: Service
metadata:
name: etcd
namespace: fission
labels:
svc: etcd
spec:
ports:
- port: 2379
targetPort: 2379
selector:
svc: etcd
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: etcd
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: etcd
spec:
containers:
- name: etcd
image: quay.io/coreos/etcd
env:
- name: ETCD_LISTEN_CLIENT_URLS
value: http://0.0.0.0:2379
- name: ETCD_ADVERTISE_CLIENT_URLS
value: http://etcd:2379
- name: ETCD_DATA_DIR
value: /tmp/default.etcd
---
apiVersion: v1
groupNames: null
kind: RoleBinding
metadata:
name: fission:poolmgr
namespace: fission-function
roleRef:
name: fission:poolmgr
subjects:
- kind: ServiceAccount
name: poolmgr
namespace: fission
userNames:
- system:serviceaccount:fission:poolmgr
-159
View File
@@ -1,159 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: fission
labels:
name: fission
---
apiVersion: v1
kind: Namespace
metadata:
name: fission-function
labels:
name: fission-function
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: fission-svc
namespace: fission
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1alpha1
metadata:
name: fission-admin
namespace: fission
subjects:
- kind: ServiceAccount
name: fission-svc
namespace: fission
roleRef:
kind: ClusterRole
name: admin
apiGroup: rbac.authorization.k8s.io
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1alpha1
metadata:
name: fission-function-admin
namespace: fission-function
subjects:
- kind: ServiceAccount
name: fission-svc
namespace: fission
roleRef:
kind: ClusterRole
name: admin
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: fission-fetcher
namespace: fission-function
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: fission-fetcher-tpr
subjects:
- kind: ServiceAccount
name: fission-fetcher
namespace: fission-function
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: controller
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: controller
spec:
containers:
- name: controller
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--controllerPort", "8888"]
serviceAccountName: fission-svc
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: router
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: router
spec:
containers:
- name: router
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--routerPort", "8888"]
serviceAccountName: fission-svc
---
apiVersion: v1
kind: Service
metadata:
name: poolmgr
namespace: fission
labels:
svc: poolmgr
spec:
ports:
- port: 80
targetPort: 8888
selector:
svc: poolmgr
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: poolmgr
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: poolmgr
spec:
containers:
- name: poolmgr
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--poolmgrPort", "8888"]
env:
- name: FETCHER_IMAGE
value: fission/fetcher:v2
serviceAccountName: fission-svc
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: kubewatcher
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: kubewatcher
spec:
containers:
- name: kubewatcher
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--kubewatcher"]
serviceAccountName: fission-svc
-127
View File
@@ -1,127 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: fission
labels:
name: fission
---
apiVersion: v1
kind: Namespace
metadata:
name: fission-function
labels:
name: fission-function
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: controller
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: controller
spec:
containers:
- name: controller
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--controllerPort", "8888"]
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: router
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: router
spec:
containers:
- name: router
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--routerPort", "8888"]
---
apiVersion: v1
kind: Service
metadata:
name: poolmgr
namespace: fission
labels:
svc: poolmgr
spec:
ports:
- port: 80
targetPort: 8888
selector:
svc: poolmgr
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: poolmgr
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: poolmgr
spec:
containers:
- name: poolmgr
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--poolmgrPort", "8888"]
env:
- name: FETCHER_IMAGE
value: fission/fetcher:v2
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: kubewatcher
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: kubewatcher
spec:
containers:
- name: kubewatcher
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--kubewatcher"]
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: timer
namespace: fission
spec:
replicas: 1
template:
metadata:
labels:
svc: timer
spec:
containers:
- name: timer
image: fission/fission-bundle
command: ["/fission-bundle"]
args: ["--timer"]
+20 -6
View File
@@ -18,7 +18,6 @@ package main
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"text/tabwriter" "text/tabwriter"
@@ -42,12 +41,22 @@ func envCreate(c *cli.Context) error {
fatal("Need an image, use --image.") fatal("Need an image, use --image.")
} }
envVersion := c.Int("version")
envBuilderImg := c.String("builder") envBuilderImg := c.String("builder")
envBuildCmd := c.String("buildcmd") envBuildCmd := c.String("buildcmd")
if len(envBuilderImg) > 0 && len(envBuildCmd) == 0 {
log.Printf("No build command is specified, use the default build command.") if len(envBuilderImg) > 0 {
envBuildCmd = "build" envVersion = 2
if len(envBuildCmd) == 0 {
envBuildCmd = "build"
}
}
// Environment API interface version is not specified and
// builder image is empty, set default interface version
if envVersion == 0 {
fmt.Println("Use default environment v1 API interface")
envVersion = 1
} }
env := &tpr.Environment{ env := &tpr.Environment{
@@ -56,7 +65,7 @@ func envCreate(c *cli.Context) error {
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
}, },
Spec: fission.EnvironmentSpec{ Spec: fission.EnvironmentSpec{
Version: 1, Version: envVersion,
Runtime: fission.Runtime{ Runtime: fission.Runtime{
Image: envImg, Image: envImg,
}, },
@@ -121,6 +130,11 @@ func envUpdate(c *cli.Context) error {
if len(envImg) > 0 { if len(envImg) > 0 {
env.Spec.Runtime.Image = envImg env.Spec.Runtime.Image = envImg
} }
if env.Spec.Version == 1 && (len(envBuilderImg) > 0 || len(envBuildCmd) > 0) {
fatal("Environment v1 API interface doesn't supported environment builder.")
}
if len(envBuilderImg) > 0 { if len(envBuilderImg) > 0 {
env.Spec.Builder.Image = envBuilderImg env.Spec.Builder.Image = envBuilderImg
} }
+127 -62
View File
@@ -18,14 +18,16 @@ package main
import ( import (
"context" "context"
"crypto/sha256"
"encoding/hex"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"strings" "strings"
"text/tabwriter" "text/tabwriter"
"time" "time"
"github.com/dchest/uniuri"
"github.com/satori/go.uuid" "github.com/satori/go.uuid"
"github.com/urfave/cli" "github.com/urfave/cli"
"k8s.io/client-go/1.5/pkg/api" "k8s.io/client-go/1.5/pkg/api"
@@ -62,10 +64,67 @@ func createArchive(client *client.Client, fileName string) *fission.Archive {
archive.Type = fission.ArchiveTypeUrl archive.Type = fission.ArchiveTypeUrl
archive.URL = archiveUrl archive.URL = archiveUrl
f, err := os.Open(fileName)
if err != nil {
checkErr(err, fmt.Sprintf("find file %v", fileName))
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
checkErr(err, fmt.Sprintf("calculate checksum for file %v", fileName))
}
archive.Checksum = fission.Checksum{
Type: fission.ChecksumTypeSHA256,
Sum: hex.EncodeToString(h.Sum(nil)),
}
} }
return &archive return &archive
} }
func createPackage(client *client.Client, envName, srcArchiveName, deployArchiveName, buildcmd string) *api.ObjectMeta {
pkgSpec := fission.PackageSpec{
Environment: fission.EnvironmentReference{
Namespace: api.NamespaceDefault,
Name: envName,
},
}
var pkgStatus fission.BuildStatus = fission.BuildStatusSucceeded
if len(deployArchiveName) > 0 {
pkgSpec.Deployment = *createArchive(client, deployArchiveName)
if len(srcArchiveName) > 0 {
fmt.Println("Deployment may be overwritten by builder manager after source package compilation")
}
}
if len(srcArchiveName) > 0 {
pkgSpec.Source = *createArchive(client, srcArchiveName)
// set pending status to package
pkgStatus = fission.BuildStatusPending
}
if len(buildcmd) > 0 {
pkgSpec.BuildCommand = buildcmd
}
pkgName := strings.ToLower(uuid.NewV4().String())
pkg := &tpr.Package{
Metadata: api.ObjectMeta{
Name: pkgName,
Namespace: api.NamespaceDefault,
},
Spec: pkgSpec,
Status: fission.PackageStatus{
BuildStatus: pkgStatus,
},
}
pkgMetadata, err := client.PackageCreate(pkg)
checkErr(err, "create package")
return pkgMetadata
}
func getContents(filePath string) []byte { func getContents(filePath string) []byte {
var code []byte var code []byte
var err error var err error
@@ -78,6 +137,10 @@ func getContents(filePath string) []byte {
func fnCreate(c *cli.Context) error { func fnCreate(c *cli.Context) error {
client := getClient(c.GlobalString("server")) client := getClient(c.GlobalString("server"))
if len(c.String("package")) > 0 {
fatal("--package is deprecated, please use --deploy instead.")
}
fnName := c.String("name") fnName := c.String("name")
if len(fnName) == 0 { if len(fnName) == 0 {
fatal("Need --name argument.") fatal("Need --name argument.")
@@ -88,17 +151,24 @@ func fnCreate(c *cli.Context) error {
fatal("Need --env argument.") fatal("Need --env argument.")
} }
srcPkgName := c.String("srcpkg") srcArchiveName := c.String("src")
deployArchiveName := c.String("code")
deployPkgName := c.String("code") if len(deployArchiveName) == 0 {
if len(deployPkgName) == 0 { deployArchiveName = c.String("deploy")
deployPkgName = c.String("package")
} }
if len(srcPkgName) == 0 && len(deployPkgName) == 0 { if len(srcArchiveName) == 0 && len(deployArchiveName) == 0 {
fatal("Need --code or --package to specify deployment package, or use --srcpkg to specify source package.") fatal("Need --code or --deploy to specify deployment archive, or use --src to specify source archive.")
} }
entrypoint := c.String("entrypoint")
buildcmd := c.String("buildcmd")
if len(buildcmd) == 0 {
buildcmd = "/builder"
}
pkgMetadata := createPackage(client, envName, srcArchiveName, deployArchiveName, buildcmd)
function := &tpr.Function{ function := &tpr.Function{
Metadata: api.ObjectMeta{ Metadata: api.ObjectMeta{
Name: fnName, Name: fnName,
@@ -109,35 +179,18 @@ func fnCreate(c *cli.Context) error {
Name: envName, Name: envName,
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
}, },
Package: fission.FunctionPackageRef{
FunctionName: entrypoint,
PackageRef: fission.PackageRef{
Namespace: pkgMetadata.Namespace,
Name: pkgMetadata.Name,
ResourceVersion: pkgMetadata.ResourceVersion,
},
},
}, },
} }
var pkgSpec fission.PackageSpec _, err := client.FunctionCreate(function)
if len(srcPkgName) > 0 {
pkgSpec.Source = *createArchive(client, srcPkgName)
}
if len(deployPkgName) > 0 {
pkgSpec.Deployment = *createArchive(client, deployPkgName)
}
pkgName := fmt.Sprintf("%v-%v", fnName, strings.ToLower(uniuri.NewLen(6)))
pkg := &tpr.Package{
Metadata: api.ObjectMeta{
Name: pkgName,
Namespace: api.NamespaceDefault,
},
Spec: pkgSpec,
}
newpkg, err := client.PackageCreate(pkg)
checkErr(err, "create package")
function.Spec.Package = fission.FunctionPackageRef{
PackageRef: fission.PackageRef{
Name: newpkg.Name,
Namespace: newpkg.Namespace,
ResourceVersion: newpkg.ResourceVersion,
},
}
_, err = client.FunctionCreate(function)
checkErr(err, "create function") checkErr(err, "create function")
fmt.Printf("function '%v' created\n", fnName) fmt.Printf("function '%v' created\n", fnName)
@@ -229,6 +282,10 @@ func fnUpdate(c *cli.Context) error {
fatal("Need name of function, use --name") fatal("Need name of function, use --name")
} }
if len(c.String("package")) > 0 {
fatal("--package is deprecated, please use --deploy instead.")
}
function, err := client.FunctionGet(&api.ObjectMeta{ function, err := client.FunctionGet(&api.ObjectMeta{
Name: fnName, Name: fnName,
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
@@ -236,42 +293,50 @@ func fnUpdate(c *cli.Context) error {
checkErr(err, fmt.Sprintf("read function '%v'", fnName)) checkErr(err, fmt.Sprintf("read function '%v'", fnName))
envName := c.String("env") envName := c.String("env")
deployPkgName := c.String("code") deployArchiveName := c.String("code")
if len(deployPkgName) == 0 { if len(deployArchiveName) == 0 {
deployPkgName = c.String("package") deployArchiveName = c.String("deploy")
} }
srcPkgName := c.String("srcpkg") srcArchiveName := c.String("src")
if len(envName) == 0 && len(deployPkgName) == 0 && len(srcPkgName) == 0 { if len(envName) == 0 && len(deployArchiveName) == 0 && len(srcArchiveName) == 0 {
fatal("Need --env or --code or --package or --srcpkg argument.") fatal("Need --env or --code or --package or --deploy argument.")
}
if len(deployPkgName) != 0 || len(srcPkgName) != 0 {
// get existing package
pkg, err := client.PackageGet(&api.ObjectMeta{
Name: function.Spec.Package.PackageRef.Name,
Namespace: function.Spec.Package.PackageRef.Namespace,
})
// update package spec
if len(srcPkgName) > 0 {
archive := createArchive(client, srcPkgName)
pkg.Spec.Source = *archive
}
if len(deployPkgName) > 0 {
archive := createArchive(client, deployPkgName)
pkg.Spec.Deployment = *archive
}
// updage package object
newpkg, err := client.PackageUpdate(pkg)
checkErr(err, "update package")
// update function spec with resource version
function.Spec.Package.PackageRef.ResourceVersion = newpkg.ResourceVersion
} }
if len(envName) > 0 { if len(envName) > 0 {
function.Spec.Environment.Name = envName function.Spec.Environment.Name = envName
} }
entrypoint := c.String("entrypoint")
if len(entrypoint) > 0 {
function.Spec.Package.FunctionName = entrypoint
}
pkg, err := client.PackageGet(&api.ObjectMeta{
Name: function.Spec.Package.PackageRef.Name,
Namespace: function.Spec.Package.PackageRef.Namespace,
})
checkErr(err, fmt.Sprintf("read package '%v'", function.Spec.Package.PackageRef.Name))
buildcmd := c.String("buildcmd")
if len(buildcmd) == 0 {
// use previous build command if not specified.
buildcmd = pkg.Spec.BuildCommand
}
if len(deployArchiveName) > 0 || len(srcArchiveName) > 0 {
// create a new package for function
pkgMetadata := createPackage(client,
function.Spec.Environment.Name, srcArchiveName, deployArchiveName, buildcmd)
// update function spec with resource version
function.Spec.Package.PackageRef = fission.PackageRef{
Namespace: pkgMetadata.Namespace,
Name: pkgMetadata.Name,
ResourceVersion: pkgMetadata.ResourceVersion,
}
}
_, err = client.FunctionUpdate(function) _, err = client.FunctionUpdate(function)
checkErr(err, "update function") checkErr(err, "update function")
+11 -7
View File
@@ -26,7 +26,7 @@ func main() {
app := cli.NewApp() app := cli.NewApp()
app.Name = "fission" app.Name = "fission"
app.Usage = "Serverless functions for Kubernetes" app.Usage = "Serverless functions for Kubernetes"
app.Version = "0.2.1" app.Version = "0.3.0"
app.Flags = []cli.Flag{ app.Flags = []cli.Flag{
cli.StringFlag{Name: "server", Usage: "Fission server URL", EnvVar: "FISSION_URL"}, cli.StringFlag{Name: "server", Usage: "Fission server URL", EnvVar: "FISSION_URL"},
@@ -40,17 +40,20 @@ func main() {
fnNameFlag := cli.StringFlag{Name: "name", Usage: "function name"} fnNameFlag := cli.StringFlag{Name: "name", Usage: "function name"}
fnEnvNameFlag := cli.StringFlag{Name: "env", Usage: "environment name for function"} fnEnvNameFlag := cli.StringFlag{Name: "env", Usage: "environment name for function"}
fnCodeFlag := cli.StringFlag{Name: "code", Usage: "local path or URL for source code"} fnCodeFlag := cli.StringFlag{Name: "code", Usage: "local path or URL for source code"}
fnPackageFlag := cli.StringFlag{Name: "package", Usage: "local path or URL for binary package"} fnPackageFlag := cli.StringFlag{Name: "package", Usage: "(Deprecated) local path or URL for binary package"}
fnSrcPackageFlag := cli.StringFlag{Name: "srcpkg", Usage: "local path or URL for source package"} fnDeployArchiveFlag := cli.StringFlag{Name: "deployarchive, deploy", Usage: "local path or URL for deployment archive"}
fnSrcArchiveFlag := cli.StringFlag{Name: "sourcearchive, src", Usage: "local path or URL for source archive"}
fnPodFlag := cli.StringFlag{Name: "pod", Usage: "function pod name, optional (use latest if unspecified)"} fnPodFlag := cli.StringFlag{Name: "pod", Usage: "function pod name, optional (use latest if unspecified)"}
fnFollowFlag := cli.BoolFlag{Name: "follow, f", Usage: "specify if the logs should be streamed"} fnFollowFlag := cli.BoolFlag{Name: "follow, f", Usage: "specify if the logs should be streamed"}
fnDetailFlag := cli.BoolFlag{Name: "detail, d", Usage: "display detailed information"} fnDetailFlag := cli.BoolFlag{Name: "detail, d", Usage: "display detailed information"}
fnLogDBTypeFlag := cli.StringFlag{Name: "dbtype", Usage: "log database type, e.g. influxdb (currently only influxdb is supported)"} fnLogDBTypeFlag := cli.StringFlag{Name: "dbtype", Usage: "log database type, e.g. influxdb (currently only influxdb is supported)"}
fnEntryPointFlag := cli.StringFlag{Name: "entrypoint", Usage: "entry point for environment v2 to load with"}
fnBuildCmdFlag := cli.StringFlag{Name: "buildcmd", Usage: "build command for builder to run with"}
fnSubcommands := []cli.Command{ fnSubcommands := []cli.Command{
{Name: "create", Usage: "Create new function (and optionally, an HTTP route to it)", Flags: []cli.Flag{fnNameFlag, fnEnvNameFlag, fnCodeFlag, fnPackageFlag, fnSrcPackageFlag, htUrlFlag, htMethodFlag}, Action: fnCreate}, {Name: "create", Usage: "Create new function (and optionally, an HTTP route to it)", Flags: []cli.Flag{fnNameFlag, fnEnvNameFlag, fnCodeFlag, fnPackageFlag, fnSrcArchiveFlag, fnDeployArchiveFlag, fnEntryPointFlag, fnBuildCmdFlag, htUrlFlag, htMethodFlag}, Action: fnCreate},
{Name: "get", Usage: "Get function source code", Flags: []cli.Flag{fnNameFlag}, Action: fnGet}, {Name: "get", Usage: "Get function source code", Flags: []cli.Flag{fnNameFlag}, Action: fnGet},
{Name: "getmeta", Usage: "Get function metadata", Flags: []cli.Flag{fnNameFlag}, Action: fnGetMeta}, {Name: "getmeta", Usage: "Get function metadata", Flags: []cli.Flag{fnNameFlag}, Action: fnGetMeta},
{Name: "update", Usage: "Update function source code", Flags: []cli.Flag{fnNameFlag, fnEnvNameFlag, fnCodeFlag, fnPackageFlag, fnSrcPackageFlag}, Action: fnUpdate}, {Name: "update", Usage: "Update function source code", Flags: []cli.Flag{fnNameFlag, fnEnvNameFlag, fnCodeFlag, fnPackageFlag, fnSrcArchiveFlag, fnDeployArchiveFlag, fnEntryPointFlag, fnBuildCmdFlag}, Action: fnUpdate},
{Name: "delete", Usage: "Delete function", Flags: []cli.Flag{fnNameFlag}, Action: fnDelete}, {Name: "delete", Usage: "Delete function", Flags: []cli.Flag{fnNameFlag}, Action: fnDelete},
{Name: "list", Usage: "List all functions", Flags: []cli.Flag{}, Action: fnList}, {Name: "list", Usage: "List all functions", Flags: []cli.Flag{}, Action: fnList},
{Name: "logs", Usage: "Display function logs", Flags: []cli.Flag{fnNameFlag, fnPodFlag, fnFollowFlag, fnDetailFlag, fnLogDBTypeFlag}, Action: fnLogs}, {Name: "logs", Usage: "Display function logs", Flags: []cli.Flag{fnNameFlag, fnPodFlag, fnFollowFlag, fnDetailFlag, fnLogDBTypeFlag}, Action: fnLogs},
@@ -100,8 +103,9 @@ func main() {
envImageFlag := cli.StringFlag{Name: "image", Usage: "Environment image URL"} envImageFlag := cli.StringFlag{Name: "image", Usage: "Environment image URL"}
envBuilderImageFlag := cli.StringFlag{Name: "builder", Usage: "Environment builder image URL (optional)"} envBuilderImageFlag := cli.StringFlag{Name: "builder", Usage: "Environment builder image URL (optional)"}
envBuildCmdFlag := cli.StringFlag{Name: "buildcmd", Usage: "Build command for environment builder to build source package (optional)"} envBuildCmdFlag := cli.StringFlag{Name: "buildcmd", Usage: "Build command for environment builder to build source package (optional)"}
envVersionFlag := cli.IntFlag{Name: "version", Usage: "Environment API version: defaults to 1 (means v1 interface)"}
envSubcommands := []cli.Command{ envSubcommands := []cli.Command{
{Name: "create", Aliases: []string{"add"}, Usage: "Add an environment", Flags: []cli.Flag{envNameFlag, envImageFlag, envBuilderImageFlag, envBuildCmdFlag}, Action: envCreate}, {Name: "create", Aliases: []string{"add"}, Usage: "Add an environment", Flags: []cli.Flag{envNameFlag, envImageFlag, envBuilderImageFlag, envBuildCmdFlag, envVersionFlag}, Action: envCreate},
{Name: "get", Usage: "Get environment details", Flags: []cli.Flag{envNameFlag}, Action: envGet}, {Name: "get", Usage: "Get environment details", Flags: []cli.Flag{envNameFlag}, Action: envGet},
{Name: "update", Usage: "Update environment", Flags: []cli.Flag{envNameFlag, envImageFlag, envBuilderImageFlag, envBuildCmdFlag}, Action: envUpdate}, {Name: "update", Usage: "Update environment", Flags: []cli.Flag{envNameFlag, envImageFlag, envBuilderImageFlag, envBuildCmdFlag}, Action: envUpdate},
{Name: "delete", Usage: "Delete environment", Flags: []cli.Flag{envNameFlag}, Action: envDelete}, {Name: "delete", Usage: "Delete environment", Flags: []cli.Flag{envNameFlag}, Action: envDelete},
@@ -125,7 +129,7 @@ func main() {
upgradeFileFlag := cli.StringFlag{Name: "file", Usage: "JSON file containing all fission state"} upgradeFileFlag := cli.StringFlag{Name: "file", Usage: "JSON file containing all fission state"}
upgradeSubCommands := []cli.Command{ upgradeSubCommands := []cli.Command{
{Name: "dump", Usage: "Dump all state from a v0.1 fission installation", Flags: []cli.Flag{upgradeFileFlag}, Action: upgradeDumpState}, {Name: "dump", Usage: "Dump all state from a v0.1 fission installation", Flags: []cli.Flag{upgradeFileFlag}, Action: upgradeDumpState},
{Name: "restore", Usage: "Restore state dumped from a v0.1 install into a v0.2 install", Flags: []cli.Flag{upgradeFileFlag}, Action: upgradeRestoreState}, {Name: "restore", Usage: "Restore state dumped from a v0.1 install into a v0.2+ install", Flags: []cli.Flag{upgradeFileFlag}, Action: upgradeRestoreState},
} }
app.Commands = []cli.Command{ app.Commands = []cli.Command{
{Name: "function", Aliases: []string{"fn"}, Usage: "Create, update and manage functions", Subcommands: fnSubcommands}, {Name: "function", Aliases: []string{"fn"}, Usage: "Create, update and manage functions", Subcommands: fnSubcommands},
+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/env bash
#
# deploy.sh - (Almost) automatic setup of a Fission Workflows deployment
#
# Configs
FISSION_VERSION=0.3.0-rc
FISSION_WORKFLOWS_VERSION=0.1.0
echo "Fission Workflows Deploy Script v1"
# Check if kubectl is installed
if ! command -v kubectl >/dev/null 2>&1; then
echo "kubectl is not installed"
exit 1;
fi
# Check if minikube is installed
if ! command -v minikube >/dev/null 2>&1 ; then
echo "minikube is not installed."
exit 1;
fi
# Check if helm is installed
if ! command -v helm >/dev/null 2>&1 ; then
echo "helm is not installed."
exit 1;
fi
# Ensure that minikube is running
if ! minikube ip >/dev/null 2>&1 ; then
echo "Starting minikube cluster..."
minikube start
fi
# Install helm on cluster
if ! helm list >/dev/null 2>&1 ; then
echo "Installing helm..."
kubectl -n kube-system create sa tiller
kubectl create clusterrolebinding tiller --clusterrole cluster-admin --serviceaccount=kube-system:tiller
helm init --service-account tiller
printf "Waiting for Helm"
until helm list >/dev/null 2>&1
do
printf "."
sleep 3
done
printf "\n"
fi
# Output debug logs
echo "---------- Debug ----------"
minikube version
printf "K8S "
kubectl version --client --short
printf "K8S "
kubectl version --short | grep Server
printf "Helm "
helm version --short -c
printf "Helm "
helm version --short -s
echo "Fission: ${FISSION_VERSION}"
echo "Fission Workflows: ${FISSION_WORKFLOWS_VERSION}"
echo "---------------------------"
# Ensure that fission-charts helm repo is added
if ! helm repo list | grep fission-charts >/dev/null 2>&1 ; then
echo "Setting up fission-charts Helm repo..."
helm repo add fission-charts https://fission.github.io/fission-charts/
helm repo update
fi
# Install Fission
if ! fission fn list >/dev/null 2>&1 ; then
echo "Installing Fission..."
helm install --namespace fission --set serviceType=NodePort -n fission-all fission-charts/fission-all --wait --version ${FISSION_VERSION}
fi
# Install Fission Workflows
if ! fission env get --name workflow >/dev/null 2>&1 ; then
echo "Installing Fission Workflows..."
if [[ -z "${FISSION_WORKFLOWS_VERSION// }" ]] ; then
helm install -n fission-workflows fission-charts/fission-workflows --wait --version ${FISSION_WORKFLOWS_VERSION}
else
helm install -n fission-workflows fission-charts/fission-workflows --wait
fi
fi
+3
View File
@@ -106,7 +106,9 @@ build_and_push_env_image() {
./build.sh ./build.sh
fi fi
docker build -t fission/$imgname:$version . docker build -t fission/$imgname:$version .
docker tag fission/$imgname:$version fission/$imgname:latest
docker push fission/$imgname:$version docker push fission/$imgname:$version
docker push fission/$imgname:latest
popd popd
} }
@@ -116,6 +118,7 @@ build_and_push_all_envs() {
build_and_push_env_image $version nodejs node-env build_and_push_env_image $version nodejs node-env
build_and_push_env_image $version binary binary-env build_and_push_env_image $version binary binary-env
build_and_push_env_image $version dotnet dotnet-env build_and_push_env_image $version dotnet dotnet-env
build_and_push_env_image $version dotnet20 dotnet20-env
build_and_push_env_image $version go go-env build_and_push_env_image $version go go-env
build_and_push_env_image $version perl perl-env build_and_push_env_image $version perl perl-env
build_and_push_env_image $version php7 php-env build_and_push_env_image $version php7 php-env
+3 -2
View File
@@ -165,8 +165,9 @@ func (mqt *MessageQueueTriggerManager) syncTriggers() {
log.Fatalf("Failed to read message queue trigger list: %v", err) log.Fatalf("Failed to read message queue trigger list: %v", err)
} }
newTriggerMap := make(map[string]*tpr.Messagequeuetrigger) newTriggerMap := make(map[string]*tpr.Messagequeuetrigger)
for _, trigger := range newTriggers.Items { for index := range newTriggers.Items {
newTriggerMap[tpr.CacheKey(&trigger.Metadata)] = &trigger newTrigger := &newTriggers.Items[index]
newTriggerMap[tpr.CacheKey(&newTrigger.Metadata)] = newTrigger
} }
// get current set of triggers // get current set of triggers
+20 -2
View File
@@ -22,6 +22,7 @@ import (
"k8s.io/client-go/1.5/pkg/api" "k8s.io/client-go/1.5/pkg/api"
"github.com/fission/fission"
"github.com/fission/fission/cache" "github.com/fission/fission/cache"
"github.com/fission/fission/tpr" "github.com/fission/fission/tpr"
) )
@@ -58,6 +59,7 @@ type (
address string address string
podName string podName string
age time.Duration age time.Duration
env *api.ObjectMeta // used for ListOld
responseChannel chan *fscResponse responseChannel chan *fscResponse
} }
fscResponse struct { fscResponse struct {
@@ -97,7 +99,9 @@ func (fsc *functionServiceCache) service() {
resp.error = err resp.error = err
} else { } else {
fsvc := fsvcI.(*funcSvc) fsvc := fsvcI.(*funcSvc)
if time.Now().Sub(fsvc.atime) > req.age { if fsvc.environment.Metadata.UID == req.env.UID &&
time.Now().Sub(fsvc.atime) > req.age {
podName := podNameI.(string) podName := podNameI.(string)
pods = append(pods, podName) pods = append(pods, podName)
} }
@@ -134,6 +138,7 @@ func (fsc *functionServiceCache) GetByFunction(m *api.ObjectMeta) (*funcSvc, err
return &fsvcCopy, nil return &fsvcCopy, nil
} }
// TODO: error should be second return
func (fsc *functionServiceCache) Add(fsvc funcSvc) (error, *funcSvc) { func (fsc *functionServiceCache) Add(fsvc funcSvc) (error, *funcSvc) {
err, existing := fsc.byFunction.Set(tpr.CacheKey(fsvc.function), &fsvc) err, existing := fsc.byFunction.Set(tpr.CacheKey(fsvc.function), &fsvc)
if err != nil { if err != nil {
@@ -152,13 +157,25 @@ func (fsc *functionServiceCache) Add(fsvc funcSvc) (error, *funcSvc) {
fsvc.ctime = now fsvc.ctime = now
fsvc.atime = now fsvc.atime = now
// Add to byAddress and byPod caches. Ignore NameExists errors
// because of multiple-specialization. See issue #331.
err, _ = fsc.byAddress.Set(fsvc.address, *fsvc.function) err, _ = fsc.byAddress.Set(fsvc.address, *fsvc.function)
if err != nil { if err != nil {
if fe, ok := err.(fission.Error); ok {
if fe.Code == fission.ErrorNameExists {
err = nil
}
}
log.Printf("error caching fsvc: %v", err) log.Printf("error caching fsvc: %v", err)
return err, nil return err, nil
} }
err, _ = fsc.byPod.Set(fsvc.podName, *fsvc.function) err, _ = fsc.byPod.Set(fsvc.podName, *fsvc.function)
if err != nil { if err != nil {
if fe, ok := err.(fission.Error); ok {
if fe.Code == fission.ErrorNameExists {
err = nil
}
}
log.Printf("error caching fsvc: %v", err) log.Printf("error caching fsvc: %v", err)
return err, nil return err, nil
} }
@@ -227,11 +244,12 @@ func (fsc *functionServiceCache) _deleteByPod(podName string, minAge time.Durati
return true, nil return true, nil
} }
func (fsc *functionServiceCache) ListOld(age time.Duration) ([]string, error) { func (fsc *functionServiceCache) ListOld(env *api.ObjectMeta, age time.Duration) ([]string, error) {
responseChannel := make(chan *fscResponse) responseChannel := make(chan *fscResponse)
fsc.requestChannel <- &fscRequest{ fsc.requestChannel <- &fscRequest{
requestType: LISTOLD, requestType: LISTOLD,
age: age, age: age,
env: env,
responseChannel: responseChannel, responseChannel: responseChannel,
} }
resp := <-responseChannel resp := <-responseChannel
+104 -47
View File
@@ -27,6 +27,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"path/filepath"
"strings" "strings"
"time" "time"
@@ -61,10 +62,13 @@ type (
poolInstanceId string // small random string to uniquify pod names poolInstanceId string // small random string to uniquify pod names
fetcherImage string fetcherImage string
fetcherImagePullPolicy v1.PullPolicy fetcherImagePullPolicy v1.PullPolicy
runtimeImagePullPolicy v1.PullPolicy // pull policy for generic pool to created env deployment
kubernetesClient *kubernetes.Clientset kubernetesClient *kubernetes.Clientset
fissionClient *tpr.FissionClient
instanceId string // poolmgr instance id instanceId string // poolmgr instance id
labelsForPool map[string]string labelsForPool map[string]string
requestChannel chan *choosePodRequest requestChannel chan *choosePodRequest
sharedMountPath string
} }
// serialize the choosing of pods so that choices don't conflict // serialize the choosing of pods so that choices don't conflict
@@ -78,7 +82,19 @@ type (
} }
) )
func getImagePullPolicy(policy string) v1.PullPolicy {
switch policy {
case "Always":
return v1.PullAlways
case "Never":
return v1.PullNever
default:
return v1.PullIfNotPresent
}
}
func MakeGenericPool( func MakeGenericPool(
fissionClient *tpr.FissionClient,
kubernetesClient *kubernetes.Clientset, kubernetesClient *kubernetes.Clientset,
env *tpr.Environment, env *tpr.Environment,
initialReplicas int32, initialReplicas int32,
@@ -92,9 +108,13 @@ func MakeGenericPool(
if len(fetcherImage) == 0 { if len(fetcherImage) == 0 {
fetcherImage = "fission/fetcher" fetcherImage = "fission/fetcher"
} }
fetcherImagePullPolicyS := os.Getenv("FETCHER_IMAGE_PULL_POLICY") fetcherImagePullPolicy := os.Getenv("FETCHER_IMAGE_PULL_POLICY")
if len(fetcherImagePullPolicyS) == 0 { if len(fetcherImagePullPolicy) == 0 {
fetcherImagePullPolicyS = "IfNotPresent" fetcherImagePullPolicy = "IfNotPresent"
}
runtimeImagePullPolicy := os.Getenv("RUNTIME_IMAGE_PULL_POLICY")
if len(runtimeImagePullPolicy) == 0 {
runtimeImagePullPolicy = "IfNotPresent"
} }
// TODO: in general we need to provide the user a way to configure pools. Initial // TODO: in general we need to provide the user a way to configure pools. Initial
@@ -103,6 +123,7 @@ func MakeGenericPool(
env: env, env: env,
replicas: initialReplicas, // TODO make this an env param instead? replicas: initialReplicas, // TODO make this an env param instead?
requestChannel: make(chan *choosePodRequest), requestChannel: make(chan *choosePodRequest),
fissionClient: fissionClient,
kubernetesClient: kubernetesClient, kubernetesClient: kubernetesClient,
namespace: namespace, namespace: namespace,
podReadyTimeout: 5 * time.Minute, // TODO make this an env param? podReadyTimeout: 5 * time.Minute, // TODO make this an env param?
@@ -111,18 +132,13 @@ func MakeGenericPool(
poolInstanceId: uniuri.NewLen(8), poolInstanceId: uniuri.NewLen(8),
instanceId: instanceId, instanceId: instanceId,
fetcherImage: fetcherImage, fetcherImage: fetcherImage,
useSvc: false, // defaults off -- svc takes a second or more to become routable, slowing cold start useSvc: false, // defaults off -- svc takes a second or more to become routable, slowing cold start
sharedMountPath: "/userfunc", // used by generic pool when creating env deployment to specify the share volume path for fetcher & env
} }
switch fetcherImagePullPolicyS { gp.runtimeImagePullPolicy = getImagePullPolicy(runtimeImagePullPolicy)
case "Always":
gp.fetcherImagePullPolicy = v1.PullAlways
case "Never":
gp.fetcherImagePullPolicy = v1.PullNever
default:
gp.fetcherImagePullPolicy = v1.PullIfNotPresent
}
gp.fetcherImagePullPolicy = getImagePullPolicy(fetcherImagePullPolicy)
log.Printf("fetcher image: %v, pull policy: %v", gp.fetcherImage, gp.fetcherImagePullPolicy) log.Printf("fetcher image: %v, pull policy: %v", gp.fetcherImage, gp.fetcherImagePullPolicy)
// Labels for generic deployment/RS/pods. // Labels for generic deployment/RS/pods.
@@ -141,7 +157,10 @@ func MakeGenericPool(
go gp.choosePodService() go gp.choosePodService()
go gp.idlePodReaper() // Unless specified otherwise, periodically cleanup inactive pods.
if env.Spec.AllowedFunctionsPerContainer != fission.AllowedFunctionsPerContainerInfinite {
go gp.idlePodReaper()
}
return gp, nil return gp, nil
} }
@@ -186,8 +205,7 @@ func (gp *GenericPool) _choosePod(newLabels map[string]string) (*v1.Pod, error)
// Get pods; filter the ones that are ready // Get pods; filter the ones that are ready
podList, err := gp.kubernetesClient.Core().Pods(gp.namespace).List( podList, err := gp.kubernetesClient.Core().Pods(gp.namespace).List(
api.ListOptions{ api.ListOptions{
LabelSelector: labels.Set( LabelSelector: labels.Set(gp.deployment.Spec.Selector.MatchLabels).AsSelector(),
gp.deployment.Spec.Selector.MatchLabels).AsSelector(),
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@@ -212,8 +230,7 @@ func (gp *GenericPool) _choosePod(newLabels map[string]string) (*v1.Pod, error)
readyPods = append(readyPods, &pod) readyPods = append(readyPods, &pod)
} }
} }
log.Printf("[%v] found %v ready pods of %v total", log.Printf("[%v] found %v ready pods of %v total", newLabels, len(readyPods), len(podList.Items))
newLabels, len(readyPods), len(podList.Items))
// If there are no ready pods, wait and retry. // If there are no ready pods, wait and retry.
if len(readyPods) == 0 { if len(readyPods) == 0 {
@@ -229,15 +246,17 @@ func (gp *GenericPool) _choosePod(newLabels map[string]string) (*v1.Pod, error)
// and make a good scheduling decision. // and make a good scheduling decision.
chosenPod := readyPods[rand.Intn(len(readyPods))] chosenPod := readyPods[rand.Intn(len(readyPods))]
// Relabel. If the pod already got picked and if gp.env.Spec.AllowedFunctionsPerContainer != fission.AllowedFunctionsPerContainerInfinite {
// modified, this should fail; in that case just // Relabel. If the pod already got picked and
// retry. // modified, this should fail; in that case just
chosenPod.ObjectMeta.Labels = newLabels // retry.
log.Printf("relabeling pod: [%v]", chosenPod.ObjectMeta.Name) chosenPod.ObjectMeta.Labels = newLabels
_, err = gp.kubernetesClient.Core().Pods(gp.namespace).Update(chosenPod) log.Printf("relabeling pod: [%v]", chosenPod.ObjectMeta.Name)
if err != nil { _, err = gp.kubernetesClient.Core().Pods(gp.namespace).Update(chosenPod)
log.Printf("failed to relabel pod [%v]: %v", chosenPod.ObjectMeta.Name, err) if err != nil {
continue log.Printf("failed to relabel pod [%v]: %v", chosenPod.ObjectMeta.Name, err)
continue
}
} }
log.Printf("Chosen pod: %v (in %v)", chosenPod.ObjectMeta.Name, time.Now().Sub(startTime)) log.Printf("Chosen pod: %v (in %v)", chosenPod.ObjectMeta.Name, time.Now().Sub(startTime))
return chosenPod, nil return chosenPod, nil
@@ -276,12 +295,15 @@ func (gp *GenericPool) getFetcherUrl(podIP string) string {
return fmt.Sprintf("http://%v:8000/", podIP) return fmt.Sprintf("http://%v:8000/", podIP)
} }
func (gp *GenericPool) getSpecializeUrl(podIP string) string { func (gp *GenericPool) getSpecializeUrl(podIP string, version int) string {
u := os.Getenv("TEST_SPECIALIZE_URL") u := os.Getenv("TEST_SPECIALIZE_URL")
if len(u) != 0 { if len(u) != 0 {
return u return u
} }
return fmt.Sprintf("http://%v:8888/specialize", podIP) if version == 1 {
return fmt.Sprintf("http://%v:8888/specialize", podIP)
}
return fmt.Sprintf("http://%v:8888/v%v/specialize", podIP, version)
} }
// specializePod chooses a pod, copies the required user-defined function to that pod // specializePod chooses a pod, copies the required user-defined function to that pod
@@ -297,10 +319,23 @@ func (gp *GenericPool) specializePod(pod *v1.Pod, metadata *api.ObjectMeta) erro
// tell fetcher to get the function. // tell fetcher to get the function.
fetcherUrl := gp.getFetcherUrl(podIP) fetcherUrl := gp.getFetcherUrl(podIP)
log.Printf("[%v] calling fetcher to copy function", metadata.Name) log.Printf("[%v] calling fetcher to copy function", metadata.Name)
err := fetcherClient.DoFetchRequest(fetcherUrl, &fetcher.FetchRequest{
fn, err := gp.fissionClient.
Functions(metadata.Namespace).
Get(metadata.Name)
if err != nil {
return err
}
targetFilename := "user"
err = fetcherClient.MakeClient(fetcherUrl).Fetch(&fetcher.FetchRequest{
FetchType: fetcher.FETCH_DEPLOYMENT, FetchType: fetcher.FETCH_DEPLOYMENT,
Function: *metadata, Package: api.ObjectMeta{
Filename: "user", // XXX use function id instead Namespace: fn.Spec.Package.PackageRef.Namespace,
Name: fn.Spec.Package.PackageRef.Name,
},
Filename: targetFilename, // XXX use function id instead
}) })
if err != nil { if err != nil {
return err return err
@@ -311,12 +346,30 @@ func (gp *GenericPool) specializePod(pod *v1.Pod, metadata *api.ObjectMeta) erro
// get function run container to specialize // get function run container to specialize
log.Printf("[%v] specializing pod", metadata.Name) log.Printf("[%v] specializing pod", metadata.Name)
specializeUrl := gp.getSpecializeUrl(podIP)
// retry the specialize call a few times in case the env server hasn't come up yet // retry the specialize call a few times in case the env server hasn't come up yet
maxRetries := 20 maxRetries := 20
loadReq := fission.FunctionLoadRequest{
FilePath: filepath.Join(gp.sharedMountPath, targetFilename),
FunctionName: fn.Spec.Package.FunctionName,
FunctionMetadata: &fn.Metadata,
}
body, err := json.Marshal(loadReq)
if err != nil {
return err
}
for i := 0; i < maxRetries; i++ { for i := 0; i < maxRetries; i++ {
resp2, err := http.Post(specializeUrl, "text/plain", bytes.NewReader([]byte{})) var resp2 *http.Response
if gp.env.Spec.Version == 2 {
specializeUrl := gp.getSpecializeUrl(podIP, 2)
resp2, err = http.Post(specializeUrl, "application/json", bytes.NewReader(body))
} else {
specializeUrl := gp.getSpecializeUrl(podIP, 1)
resp2, err = http.Post(specializeUrl, "text/plain", bytes.NewReader([]byte{}))
}
if err == nil && resp2.StatusCode < 300 { if err == nil && resp2.StatusCode < 300 {
// Success // Success
resp2.Body.Close() resp2.Body.Close()
@@ -352,7 +405,6 @@ func (gp *GenericPool) createPool() error {
poolDeploymentName := fmt.Sprintf("%v-%v-%v", poolDeploymentName := fmt.Sprintf("%v-%v-%v",
gp.env.Metadata.Name, gp.env.Metadata.UID, strings.ToLower(gp.poolInstanceId)) gp.env.Metadata.Name, gp.env.Metadata.UID, strings.ToLower(gp.poolInstanceId))
sharedMountPath := "/userfunc"
deployment := &v1beta1.Deployment{ deployment := &v1beta1.Deployment{
ObjectMeta: v1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: poolDeploymentName, Name: poolDeploymentName,
@@ -380,12 +432,12 @@ func (gp *GenericPool) createPool() error {
{ {
Name: gp.env.Metadata.Name, Name: gp.env.Metadata.Name,
Image: gp.env.Spec.Runtime.Image, Image: gp.env.Spec.Runtime.Image,
ImagePullPolicy: v1.PullIfNotPresent, ImagePullPolicy: gp.runtimeImagePullPolicy,
TerminationMessagePath: "/dev/termination-log", TerminationMessagePath: "/dev/termination-log",
VolumeMounts: []v1.VolumeMount{ VolumeMounts: []v1.VolumeMount{
{ {
Name: "userfunc", Name: "userfunc",
MountPath: sharedMountPath, MountPath: gp.sharedMountPath,
}, },
}, },
}, },
@@ -397,10 +449,10 @@ func (gp *GenericPool) createPool() error {
VolumeMounts: []v1.VolumeMount{ VolumeMounts: []v1.VolumeMount{
{ {
Name: "userfunc", Name: "userfunc",
MountPath: sharedMountPath, MountPath: gp.sharedMountPath,
}, },
}, },
Command: []string{"/fetcher", sharedMountPath}, Command: []string{"/fetcher", gp.sharedMountPath},
}, },
}, },
ServiceAccountName: "fission-fetcher", ServiceAccountName: "fission-fetcher",
@@ -478,7 +530,7 @@ func (gp *GenericPool) GetFuncSvc(m *api.ObjectMeta) (*funcSvc, error) {
if gp.useSvc { if gp.useSvc {
svcName := fmt.Sprintf("svc-%v", m.Name) svcName := fmt.Sprintf("svc-%v", m.Name)
if len(m.UID) > 0 { if len(m.UID) > 0 {
svcName += ("-" + string(m.UID)) svcName = fmt.Sprintf("%s-%v", svcName, m.UID)
} }
labels := gp.labelsForFunction(m) labels := gp.labelsForFunction(m)
@@ -511,13 +563,18 @@ func (gp *GenericPool) GetFuncSvc(m *api.ObjectMeta) (*funcSvc, error) {
err, existingFsvc := gp.fsCache.Add(*fsvc) err, existingFsvc := gp.fsCache.Add(*fsvc)
if err != nil { if err != nil {
// Some other thread beat us to it -- return the other thread's fsvc and clean up if fe, ok := err.(fission.Error); ok {
// our own. if fe.Code == fission.ErrorNameExists {
log.Printf("func svc already exists: %v", existingFsvc.podName) // Some other thread beat us to it -- return the other thread's fsvc and clean up
go func() { // our own.
gp.kubernetesClient.Core().Pods(gp.namespace).Delete(fsvc.podName, nil) log.Printf("func svc already exists: %v", existingFsvc.podName)
}() go func() {
return existingFsvc, nil gp.kubernetesClient.Core().Pods(gp.namespace).Delete(fsvc.podName, nil)
}()
return existingFsvc, nil
}
}
return nil, err
} }
return fsvc, nil return fsvc, nil
} }
@@ -563,7 +620,7 @@ func (gp *GenericPool) CleanupFunctionService(podName string) error {
func (gp *GenericPool) idlePodReaper() { func (gp *GenericPool) idlePodReaper() {
for { for {
time.Sleep(time.Minute) time.Sleep(time.Minute)
podNames, err := gp.fsCache.ListOld(gp.idlePodReapTime) podNames, err := gp.fsCache.ListOld(&gp.env.Metadata, gp.idlePodReapTime)
if err != nil { if err != nil {
log.Printf("Error reaping idle pods: %v", err) log.Printf("Error reaping idle pods: %v", err)
continue continue
+8 -2
View File
@@ -23,6 +23,7 @@ import (
"k8s.io/client-go/1.5/kubernetes" "k8s.io/client-go/1.5/kubernetes"
"k8s.io/client-go/1.5/pkg/api" "k8s.io/client-go/1.5/pkg/api"
"github.com/fission/fission"
"github.com/fission/fission/tpr" "github.com/fission/fission/tpr"
) )
@@ -86,9 +87,14 @@ func (gpm *GenericPoolManager) service() {
var err error var err error
pool, ok := gpm.pools[tpr.CacheKey(&req.env.Metadata)] pool, ok := gpm.pools[tpr.CacheKey(&req.env.Metadata)]
if !ok { if !ok {
var poolSize int32 = 3 // TODO configurable/autoscalable
switch req.env.Spec.AllowedFunctionsPerContainer {
case fission.AllowedFunctionsPerContainerInfinite:
poolSize = 1
}
pool, err = MakeGenericPool( pool, err = MakeGenericPool(
gpm.kubernetesClient, req.env, gpm.fissionClient, gpm.kubernetesClient, req.env, poolSize,
3, // TODO configurable/autoscalable
gpm.namespace, gpm.fsCache, gpm.instanceId) gpm.namespace, gpm.fsCache, gpm.instanceId)
if err != nil { if err != nil {
req.responseChannel <- &response{error: err} req.responseChannel <- &response{error: err}
+3
View File
@@ -98,6 +98,9 @@ func (fh *functionHandler) handler(responseWriter http.ResponseWriter, request *
request.Header.Add(fmt.Sprintf("X-Fission-Params-%v", k), v) request.Header.Add(fmt.Sprintf("X-Fission-Params-%v", k), v)
} }
// System Params
MetadataToHeaders(HEADERS_FISSION_FUNCTION_PREFIX, fh.function, request)
// cache lookup // cache lookup
serviceUrl, err := fh.fmap.lookup(fh.function) serviceUrl, err := fh.fmap.lookup(fh.function)
if err != nil { if err != nil {
+2 -1
View File
@@ -115,9 +115,10 @@ func (ts *HTTPTriggerSet) getRouter() *mux.Router {
// Internal triggers for each function by name. Non-http // Internal triggers for each function by name. Non-http
// triggers route into these. // triggers route into these.
for _, function := range ts.functions { for _, function := range ts.functions {
m := function.Metadata
fh := &functionHandler{ fh := &functionHandler{
fmap: ts.functionServiceMap, fmap: ts.functionServiceMap,
function: &function.Metadata, function: &m,
poolmgr: ts.poolmgr, poolmgr: ts.poolmgr,
} }
muxRouter.HandleFunc(fission.UrlForFunction(function.Metadata.Name), fh.handler) muxRouter.HandleFunc(fission.UrlForFunction(function.Metadata.Name), fh.handler)
+28
View File
@@ -0,0 +1,28 @@
package router
import (
"fmt"
"k8s.io/client-go/1.5/pkg/api"
"k8s.io/client-go/1.5/pkg/types"
"net/http"
)
const (
HEADERS_FISSION_FUNCTION_PREFIX = "Fission-Function"
)
func MetadataToHeaders(prefix string, meta *api.ObjectMeta, request *http.Request) {
request.Header.Add(fmt.Sprintf("X-%s-Uid", prefix), string(meta.UID))
request.Header.Add(fmt.Sprintf("X-%s-Name", prefix), meta.Name)
request.Header.Add(fmt.Sprintf("X-%s-Namespace", prefix), meta.Namespace)
request.Header.Add(fmt.Sprintf("X-%s-ResourceVersion", prefix), meta.ResourceVersion)
}
func HeadersToMetadata(prefix string, headers http.Header) *api.ObjectMeta {
return &api.ObjectMeta{
Name: headers.Get(fmt.Sprintf("X-%s-Name", prefix)),
UID: types.UID(headers.Get(fmt.Sprintf("X-%s-Uid", prefix))),
Namespace: headers.Get(fmt.Sprintf("X-%s-Namespace", prefix)),
ResourceVersion: headers.Get(fmt.Sprintf("X-%s-ResourceVersion", prefix)),
}
}
+15
View File
@@ -201,6 +201,21 @@ func MakeStorageService(sc *storageConfig) (*StorageService, error) {
ss.location = loc ss.location = loc
con, err := loc.CreateContainer(sc.containerName) con, err := loc.CreateContainer(sc.containerName)
if os.IsExist(err) {
var cons []stow.Container
var cursor string
// use location.Containers to find containers that match the prefix (container name)
cons, cursor, err = loc.Containers(sc.containerName, stow.CursorStart, 1)
if err == nil {
if !stow.IsCursorEnd(cursor) {
// Should only have one storage container
err = errors.New("Found more than one matched storage containers")
} else {
con = cons[0]
}
}
}
if err != nil { if err != nil {
log.Printf("Error initializing storage: %v", err) log.Printf("Error initializing storage: %v", err)
return nil, err return nil, err
+6
View File
@@ -12,12 +12,18 @@ source $(dirname $0)/test_utils.sh
IMAGE=gcr.io/fission-ci/fission-bundle IMAGE=gcr.io/fission-ci/fission-bundle
FETCHER_IMAGE=gcr.io/fission-ci/fetcher FETCHER_IMAGE=gcr.io/fission-ci/fetcher
PYTHON_RUNTIME_IMAGE=gcr.io/fission-ci/python-env
PYTHON_BUILDER_IMAGE=gcr.io/fission-ci/python-env-builder
TAG=test TAG=test
build_and_push_fission_bundle $IMAGE:$TAG build_and_push_fission_bundle $IMAGE:$TAG
build_and_push_fetcher $FETCHER_IMAGE:$TAG build_and_push_fetcher $FETCHER_IMAGE:$TAG
build_and_push_python_env_runtime $PYTHON_RUNTIME_IMAGE:$TAG
build_and_push_python_env_builder $PYTHON_BUILDER_IMAGE:$TAG
build_fission_cli build_fission_cli
install_and_test $IMAGE $TAG $FETCHER_IMAGE $TAG install_and_test $IMAGE $TAG $FETCHER_IMAGE $TAG
+32 -1
View File
@@ -50,6 +50,36 @@ build_and_push_fetcher() {
popd popd
} }
build_and_push_python_env_runtime() {
image_tag=$1
pushd $ROOT/environments/python3/
docker build -t $image_tag .
gcloud_login
gcloud docker -- push $image_tag
popd
}
build_and_push_python_env_builder() {
image_tag=$1
pushd $ROOT/builder/cmd
./build.sh
popd
pushd $ROOT/environments/python3/builder
builderDir=${GOPATH}/src/github.com/fission/fission/builder/cmd
cp ${builderDir}/builder .
docker build -t $image_tag .
gcloud_login
gcloud docker -- push $image_tag
popd
}
build_fission_cli() { build_fission_cli() {
pushd $ROOT/fission pushd $ROOT/fission
@@ -73,7 +103,7 @@ helm_install_fission() {
ns=f-$id ns=f-$id
fns=f-func-$id fns=f-func-$id
helmVars=image=$image,imageTag=$imageTag,fetcherImage=$fetcherImage,fetcherImageTag=$fetcherImageTag,functionNamespace=$fns,controllerPort=$controllerNodeport,routerPort=$routerNodeport,pullPolicy=Always helmVars=image=$image,imageTag=$imageTag,fetcherImage=$fetcherImage,fetcherImageTag=$fetcherImageTag,functionNamespace=$fns,controllerPort=$controllerNodeport,routerPort=$routerNodeport,pullPolicy=Always,analytics=false
helm_setup helm_setup
@@ -171,6 +201,7 @@ dump_fission_tpr() {
dump_fission_tprs() { dump_fission_tprs() {
dump_fission_tpr function.fission.io dump_fission_tpr function.fission.io
dump_fission_tpr package.fission.io
dump_fission_tpr httptrigger.fission.io dump_fission_tpr httptrigger.fission.io
dump_fission_tpr environment.fission.io dump_fission_tpr environment.fission.io
} }
+73
View File
@@ -0,0 +1,73 @@
#!/bin/bash
set -euo pipefail
# Create a function with source package in python
# to test builder manger functionality.
# There are two ways to trigger the build
# 1. manually trigger by http post
# 2. package watcher triggers the build if any changes to packages
ROOT=$(dirname $0)/../..
PYTHON_RUNTIME_IMAGE=gcr.io/fission-ci/python-env:test
PYTHON_BUILDER_IMAGE=gcr.io/fission-ci/python-env-builder:test
fn=python-srcbuild-$(date +%s)
checkFunctionResponse() {
echo "Doing an HTTP GET on the function's route"
response=$(curl http://$FISSION_ROUTER/$1)
echo "Checking for valid response"
echo $response
echo $response | grep -i "a: 1 b: {c: 3, d: 4}"
}
echo "Pre-test cleanup"
fission env delete --name python || true
echo "Creating python env"
fission env create --name python --image $PYTHON_RUNTIME_IMAGE --builder $PYTHON_BUILDER_IMAGE
trap "fission env delete --name python" EXIT
echo "Waiting for env builder to catch up"
sleep 30
echo "Creating source pacakage"
zip -jr demo-src-pkg.zip $ROOT/examples/python/sourcepkg/
echo "Creating function " $fn
fission fn create --name $fn --env python --src demo-src-pkg.zip --entrypoint "main" --buildcmd "./build.sh"
trap "fission fn delete --name $fn" EXIT
echo "Creating route"
fission route create --function $fn --url /$fn --method GET
echo "Waiting for router to catch up"
sleep 3
echo "Doing an HTTP POST on the builder manager's route to start a build"
pkg=$(kubectl --namespace default get functions $fn -o jsonpath='{.spec.package.packageref.name}')
echo $pkg
response=$(curl -X POST $FISSION_URL/proxy/buildermgr/v1/build \
-H 'content-type: application/json' \
-d "{\"package\": {\"namespace\": \"default\",\"name\": \"$pkg\"}}")
echo "Waiting for builder manager to finish the build triggered by http request"
sleep 30
checkFunctionResponse $fn
echo "Updating function " $fn
fission fn update --name $fn --src demo-src-pkg.zip
trap "fission fn delete --name $fn" EXIT
echo "Waiting for builder manager to finish the build triggered by packageWatcher"
sleep 30
checkFunctionResponse $fn
# crappy cleanup, improve this later
kubectl get httptrigger -o name | tail -1 | cut -f2 -d'/' | xargs kubectl delete httptrigger
echo "All done."
+46
View File
@@ -0,0 +1,46 @@
#!/bin/bash
#
# Create two functions, make sure their internal http triggers invoke
# them correctly.
#
set -euo pipefail
ROOT=$(dirname $0)/../..
echo "Pre-test cleanup"
fission env delete --name nodejs || true
echo "Creating nodejs env"
fission env create --name nodejs --image fission/node-env
trap "fission env delete --name nodejs" EXIT
echo "Writing functions"
f1=f1-$(date +%s)
f2=f2-$(date +%s)
echo $f1 $f2
for f in $f1 $f2
do
echo "module.exports = function(context, callback) { callback(200, \"$f\n\"); }" > $f.js
done
echo "Creating functions"
for f in $f1 $f2
do
fission fn create --name $f --env nodejs --code $f.js
trap "fission fn delete --name $f" EXIT
done
echo "Waiting for router to catch up"
sleep 3
echo "Testing internal routes"
for f in $f1 $f2
do
response=$(curl http://$FISSION_ROUTER/fission-function/$f)
echo $response | grep $f
done
echo "All done."
+2
View File
@@ -43,6 +43,8 @@ type (
unversioned.TypeMeta `json:",inline"` unversioned.TypeMeta `json:",inline"`
Metadata api.ObjectMeta `json:"metadata"` Metadata api.ObjectMeta `json:"metadata"`
Spec fission.PackageSpec `json:"spec"` Spec fission.PackageSpec `json:"spec"`
Status fission.PackageStatus `json:"status"`
} }
PackageList struct { PackageList struct {
unversioned.TypeMeta `json:",inline"` unversioned.TypeMeta `json:",inline"`
+22 -5
View File
@@ -16,6 +16,8 @@ limitations under the License.
package fission package fission
import "k8s.io/client-go/1.5/pkg/api"
type ( type (
// //
// Functions and packages // Functions and packages
@@ -65,9 +67,10 @@ type (
BuildStatus string BuildStatus string
PackageSpec struct { PackageSpec struct {
Environment EnvironmentReference `json:"environment"` Environment EnvironmentReference `json:"environment"`
Source Archive `json:"source"` Source Archive `json:"source"`
Deployment Archive `json:"deployment"` Deployment Archive `json:"deployment"`
BuildCommand string `json:"buildcmd"`
// In the future, we can have a debug build here too // In the future, we can have a debug build here too
} }
PackageStatus struct { PackageStatus struct {
@@ -162,8 +165,14 @@ type (
// Optional, but strongly encouraged. Used to populate // Optional, but strongly encouraged. Used to populate
// links from UI, CLI, etc. // links from UI, CLI, etc.
DocumentationURL string `json:"documentationurl"` DocumentationURL string `json:"documentationurl"`
// Optional
// Defaults to 'Single'
AllowedFunctionsPerContainer AllowedFunctionsPerContainer `json:"allowedFunctionsPerContainer"`
} }
AllowedFunctionsPerContainer string
// //
// Triggers // Triggers
// //
@@ -220,15 +229,18 @@ type (
// env-specific. Optional. // env-specific. Optional.
FilePath string `json:"filepath"` FilePath string `json:"filepath"`
// Entrypoint has an environment-specific meaning; // FunctionName has an environment-specific meaning;
// usually, it defines a function within a module // usually, it defines a function within a module
// containing multiple functions. Optional; default is // containing multiple functions. Optional; default is
// environment-specific. // environment-specific.
EntryPoint string `json:"entrypoint"` FunctionName string `json:"functionName"`
// URL to expose this function at. Optional; defaults // URL to expose this function at. Optional; defaults
// to "/". // to "/".
URL string `json:"url"` URL string `json:"url"`
// Metatdata
FunctionMetadata *api.ObjectMeta
} }
) )
@@ -252,6 +264,11 @@ const (
BuildStatusFailed = "failed" BuildStatusFailed = "failed"
) )
const (
AllowedFunctionsPerContainerSingle = "single"
AllowedFunctionsPerContainerInfinite = "infinite"
)
const ( const (
// FunctionReferenceFunctionName means that the function // FunctionReferenceFunctionName means that the function
// reference is simply by function name. // reference is simply by function name.