Enabling multi-tenancy for fission objects. (#655)

This feature allows creation of fission objects in different namespaces, in addition to retaining the existing behavior of creating fission objects in default namespace if user doesnt provide one. 
It also removes cluster admin roles for fission-fetcher and fission-builder Service Accounts and grants them only those privileges that they need.
This commit is contained in:
smruthi2187
2018-05-23 13:22:46 -07:00
committed by GitHub
parent c9d2757760
commit 8984e4916e
62 changed files with 1962 additions and 495 deletions
+66
View File
@@ -0,0 +1,66 @@
# Multi-tenancy in Fission
Multi-tenancy in Fission allows users to create Fission objects, i.e functions, packages, environments and triggers in different namespaces.
It mandates that a function reference secrets, configmaps and its package (if explicity referenced during function creation/updation) to be present in the same namespace as the function.
This allows user separation and prevents in-advertent access to sensitive data of other users sharing the same cluster.
However, users are allowed and encouraged to share environments to ensure optimal utilization of cluster resources. To achieve this, users can create all the necessary environments in a ns, say ns1 and then go on to create functions in different namespaces and refer to env in ns1.
Users that prefer complete isolation can create their env, functions in the same ns.
## Roles and privileges
1. Cluster-Admin Role : Fission's services need cluster-admin privileges to monitor, create, update and delete resources across namespaces.
2. Package-getter Role : This role has privileges to do a get, watch and list on fission package objects.
3. Secret-Configmap-getter Role : This role has privileges to do a get, watch and list on secrets and configmaps.
## Service Accounts
1. fission-fetcher
This SA is created in every namespace that a user creates runtime environments in.
Also created in function namespaces where a user creates functions that use NewDeploy executor backend.
2. fission-builder
This SA is created in every namespace that a user creates builder environments in.
## Role-bindings
1. Package-getter-binding
Every time a user creates a package explicity in a namespace, this rolebinding is created in package's namespace (which is also function's namespace). This grants package-getter role to fission-fetcher SA present in the referenced environment's namespace.
If the package is a source package, then, fission-builder SA present in the environment namespace is also added to this rolebinding.
Next when the user creates a function in the same namespace, if the function's executor type is newdeploy, then, the fission-fetcher SA present in function namespace is also added to the same rolebinding.
Note : For functions that have executor type poolmgr, the env pods are created in the namespace that env object is created. Whereas, for those functions that have executor type New deploy mgr, the function pods are created in the namespace that function object is created in.
This is because, poolmgr allows env sharing and optimal resource utilization. so generic env pools are created in a different namespace and all functions that prefer sharing this env pool can reference these pools.
If users require strict isolation, they can either create functions with new deploy backend, or, create env's in different namespaces and not share them across functions.
2. Secret-Configmap-getter-binding
Every time a user creates a function in a namespace, Secret-Configmap-getter-binding is created in the same namespace, granting secret-configmap-getter role to fission-fetcher SA present in the referenced environment's namespace in case the executor type is poolmgr.
If the executor type is newdeploymgr, then the same rolebinding is created in the same namespace as the function, granting the same secret-configmap-getter role to fission-fetcher SA present in the function namespace.
## Examples
1. create a generic python runtime env in ns 1 and function with poolmgr executor type in ns 2 that references it.
```bash
$ fission env create --name python --image fission/python-env --envns ns1
$ fission function create --name func1 --env python --code hello.py --fns ns2
```
2. create a builder and runtime environment in ns3, a source pkg in ns3 and a function referring to this src pkg also in ns3. (for complete isolation, all objs are in ns3)
```bash
$ fission env create --name python-builder-env --builder fission/python-builder --image fission/python-env --ns3
$ fission package create --src src-pkg.zip --env python-builder-env --buildcmd "./build.sh" --pkgns ns3
$ fission fn create --name func3 --fns ns3 --pkg $pkg --entrypoint "user.main"
```
## Note
1. To maintain backward compatibility, fission objects that are created without the ns flags are created in default namespace. Also, the run time env pods in such a case will continue to live in fission-function ns and builder env pods in fission-builder ns
2. Since all envs in a namespace have the same fission-fetcher SA mounted in them, even though multiple envs are created in a namespace and referenced by functions in different namespaces, the SA will have previleges to view those function's secrets if any.
3. Similarly, if there are multiple functions in different namespaces but all sharing an env in one namespace, the fission-fetcher SA in that namespace will have previleges to see all of their secrets.
+2 -2
View File
@@ -40,8 +40,8 @@ func Start(storageSvcUrl string, envBuilderNamespace string) error {
go envWatcher.watchEnvironments()
pkgWatcher := makePackageWatcher(fissionClient,
kubernetesClient.CoreV1().RESTClient(), envBuilderNamespace, storageSvcUrl)
go pkgWatcher.watchPackages()
kubernetesClient, envBuilderNamespace, storageSvcUrl)
go pkgWatcher.watchPackages(fissionClient, kubernetesClient, envBuilderNamespace)
select {}
}
+4 -6
View File
@@ -22,8 +22,6 @@ import (
"net/http"
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/dchest/uniuri"
"github.com/fission/fission"
"github.com/fission/fission/builder"
@@ -40,17 +38,17 @@ import (
// 3. Send upload request to fetcher to upload deployment package.
// 4. Return upload response and build logs.
// *. Return build logs and error if any one of steps above failed.
func buildPackage(fissionClient *crd.FissionClient, builderNamespace string,
func buildPackage(fissionClient *crd.FissionClient, envBuilderNamespace string,
storageSvcUrl string, pkg *crd.Package) (uploadResp *fetcher.UploadResponse, buildLogs string, err error) {
env, err := fissionClient.Environments(metav1.NamespaceDefault).Get(pkg.Spec.Environment.Name)
env, err := fissionClient.Environments(pkg.Spec.Environment.Namespace).Get(pkg.Spec.Environment.Name)
if err != nil {
e := fmt.Sprintf("Error getting environment CRD info: %v", err)
log.Println(e)
return nil, e, fission.MakeError(http.StatusInternalServerError, e)
}
svcName := fmt.Sprintf("%v-%v.%v", env.Metadata.Name, env.Metadata.ResourceVersion, builderNamespace)
svcName := fmt.Sprintf("%v-%v.%v", env.Metadata.Name, env.Metadata.ResourceVersion, envBuilderNamespace)
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))
@@ -131,7 +129,7 @@ func updatePackage(fissionClient *crd.FissionClient,
}
// update package spec
pkg, err := fissionClient.Packages(metav1.NamespaceDefault).Update(pkg)
pkg, err := fissionClient.Packages(pkg.Metadata.Namespace).Update(pkg)
if err != nil {
log.Printf("Error updating package: %v", err)
return nil, err
+104 -42
View File
@@ -42,7 +42,10 @@ const (
CLEANUP_BUILDERS
LABEL_ENV_NAME = "envName"
LABEL_ENV_NAMESPACE = "envNamespace"
LABEL_ENV_RESOURCEVERSION = "envResourceVersion"
LABEL_DEPLOYMENT_OWNER = "owner"
BUILDER_MGR = "buildermgr"
)
var (
@@ -130,14 +133,22 @@ func makeEnvironmentWatcher(fissionClient *crd.FissionClient,
return envWatcher
}
func (envw *environmentWatcher) getCacheKey(envName string, envResourceVersion string) string {
return fmt.Sprintf("%v-%v", envName, envResourceVersion)
func (envw *environmentWatcher) getCacheKey(envName string, envNamespace string, envResourceVersion string) string {
return fmt.Sprintf("%v-%v-%v", envName, envNamespace, envResourceVersion)
}
func (envw *environmentWatcher) getLabels(envName string, envResourceVersion string) map[string]string {
func (env *environmentWatcher) getLabelForDeploymentOwner() map[string]string {
return map[string]string{
LABEL_DEPLOYMENT_OWNER: BUILDER_MGR,
}
}
func (envw *environmentWatcher) getLabels(envName string, envNamespace string, envResourceVersion string) map[string]string {
return map[string]string{
LABEL_ENV_NAME: envName,
LABEL_ENV_NAMESPACE: envNamespace,
LABEL_ENV_RESOURCEVERSION: envResourceVersion,
LABEL_DEPLOYMENT_OWNER: BUILDER_MGR,
}
}
@@ -213,10 +224,17 @@ func (envw *environmentWatcher) service() {
req := <-envw.requestChan
switch req.requestType {
case GET_BUILDER:
key := envw.getCacheKey(req.env.Metadata.Name, req.env.Metadata.ResourceVersion)
// In order to support backward compatibility, for all environments with builder image created in default env,
// the pods will be created in fission-builder namespace
ns := envw.builderNamespace
if req.env.Metadata.Namespace != metav1.NamespaceDefault {
ns = req.env.Metadata.Namespace
}
key := envw.getCacheKey(req.env.Metadata.Name, ns, req.env.Metadata.ResourceVersion)
builderInfo, ok := envw.cache[key]
if !ok {
builderInfo, err := envw.createBuilder(req.env)
builderInfo, err := envw.createBuilder(req.env, ns)
if err != nil {
req.respChan <- envwResponse{err: err}
continue
@@ -229,7 +247,13 @@ func (envw *environmentWatcher) service() {
latestEnvList := make(map[string]*crd.Environment)
for i := range req.envList {
env := req.envList[i]
key := envw.getCacheKey(env.Metadata.Name, env.Metadata.ResourceVersion)
// In order to support backward compatibility, for all builder images created in default
// env, the pods are created in fission-builder namespace
ns := envw.builderNamespace
if env.Metadata.Namespace != metav1.NamespaceDefault {
ns = env.Metadata.Namespace
}
key := envw.getCacheKey(env.Metadata.Name, ns, env.Metadata.ResourceVersion)
latestEnvList[key] = &env
}
@@ -239,38 +263,40 @@ func (envw *environmentWatcher) service() {
// cache and CRD. We need to iterate over the services &
// deployments to remove both normal and orphan builders.
svcList, err := envw.getBuilderServiceList(nil)
svcList, err := envw.getBuilderServiceList(envw.getLabelForDeploymentOwner(), metav1.NamespaceAll)
if err != nil {
log.Println(err.Error())
}
for _, svc := range svcList {
envName := svc.ObjectMeta.Labels[LABEL_ENV_NAME]
envNamespace := svc.ObjectMeta.Labels[LABEL_ENV_NAMESPACE]
envResourceVersion := svc.ObjectMeta.Labels[LABEL_ENV_RESOURCEVERSION]
key := envw.getCacheKey(envName, envResourceVersion)
key := envw.getCacheKey(envName, envNamespace, envResourceVersion)
if _, ok := latestEnvList[key]; !ok {
err := envw.deleteBuilderService(svc.ObjectMeta.Labels)
err := envw.deleteBuilderServiceByName(svc.ObjectMeta.Name, svc.ObjectMeta.Namespace)
if err != nil {
log.Printf("Error removing builder service: %v", err)
}
}
delete(envw.cache, svc.ObjectMeta.Name)
delete(envw.cache, key)
}
deployList, err := envw.getBuilderDeploymentList(nil)
deployList, err := envw.getBuilderDeploymentList(envw.getLabelForDeploymentOwner(), metav1.NamespaceAll)
if err != nil {
log.Printf(err.Error())
}
for _, deploy := range deployList {
envName := deploy.ObjectMeta.Labels[LABEL_ENV_NAME]
envNamespace := deploy.ObjectMeta.Labels[LABEL_ENV_NAMESPACE]
envResourceVersion := deploy.ObjectMeta.Labels[LABEL_ENV_RESOURCEVERSION]
key := envw.getCacheKey(envName, envResourceVersion)
key := envw.getCacheKey(envName, envNamespace, envResourceVersion)
if _, ok := latestEnvList[key]; !ok {
err := envw.deleteBuilderDeployment(deploy.ObjectMeta.Labels)
err := envw.deleteBuilderDeploymentByName(deploy.ObjectMeta.Name, deploy.ObjectMeta.Namespace)
if err != nil {
log.Printf("Error removing builder deployment: %v", err)
}
}
delete(envw.cache, deploy.ObjectMeta.Name)
delete(envw.cache, key)
}
}
}
@@ -294,19 +320,19 @@ func (envw *environmentWatcher) cleanupEnvBuilders(envs []crd.Environment) {
}
}
func (envw *environmentWatcher) createBuilder(env *crd.Environment) (*builderInfo, error) {
func (envw *environmentWatcher) createBuilder(env *crd.Environment, ns string) (*builderInfo, error) {
var svc *apiv1.Service
var deploy *v1beta1.Deployment
sel := envw.getLabels(env.Metadata.Name, env.Metadata.ResourceVersion)
sel := envw.getLabels(env.Metadata.Name, ns, env.Metadata.ResourceVersion)
svcList, err := envw.getBuilderServiceList(sel)
svcList, err := envw.getBuilderServiceList(sel, ns)
if err != nil {
return nil, err
}
// there should be only one service in svcList
if len(svcList) == 0 {
svc, err = envw.createBuilderService(env)
svc, err = envw.createBuilderService(env, ns)
if err != nil {
return nil, fmt.Errorf("Error creating builder service: %v", err)
}
@@ -316,13 +342,20 @@ func (envw *environmentWatcher) createBuilder(env *crd.Environment) (*builderInf
return nil, fmt.Errorf("Found more than one builder service for environment %v", env.Metadata.Name)
}
deployList, err := envw.getBuilderDeploymentList(sel)
deployList, err := envw.getBuilderDeploymentList(sel, ns)
if err != nil {
return nil, err
}
// there should be only one deploy in deployList
if len(deployList) == 0 {
deploy, err = envw.createBuilderDeployment(env)
// create builder SA in this ns, if not already created
_, err := fission.SetupSA(envw.kubernetesClient, fission.FissionBuilderSA, ns)
if err != nil {
log.Printf("Error : %v creating %s in ns : %s", err, fission.FissionBuilderSA, ns)
return nil, err
}
deploy, err = envw.createBuilderDeployment(env, ns)
if err != nil {
return nil, fmt.Errorf("Error creating builder deployment: %v", err)
}
@@ -339,16 +372,44 @@ func (envw *environmentWatcher) createBuilder(env *crd.Environment) (*builderInf
}, nil
}
func (envw *environmentWatcher) deleteBuilderService(sel map[string]string) error {
svcList, err := envw.getBuilderServiceList(sel)
func (envw *environmentWatcher) deleteBuilderServiceByName(name, namespace string) error {
falseVal := false
delOpt := &metav1.DeleteOptions{
OrphanDependents: &falseVal,
}
err := envw.kubernetesClient.
Services(namespace).
Delete(name, delOpt)
if err != nil {
return fmt.Errorf("Error deleting builder service %s.%s: %v", name, namespace, err)
}
return nil
}
func (envw *environmentWatcher) deleteBuilderDeploymentByName(name, namespace string) error {
falseVal := false
delOpt := &metav1.DeleteOptions{
OrphanDependents: &falseVal,
}
err := envw.kubernetesClient.ExtensionsV1beta1().
Deployments(namespace).
Delete(name, delOpt)
if err != nil {
return fmt.Errorf("Error deleting builder deployment %s.%s: %v", name, namespace, err)
}
return nil
}
func (envw *environmentWatcher) deleteBuilderService(sel map[string]string, ns string) error {
svcList, err := envw.getBuilderServiceList(sel, ns)
if err != nil {
return err
}
for _, svc := range svcList {
log.Printf("Removing builder service: %v", svc.ObjectMeta.Name)
err = envw.kubernetesClient.
Services(envw.builderNamespace).
Delete(svc.ObjectMeta.Name, nil)
Services(ns).
Delete(svc.ObjectMeta.Name, &delOpt)
if err != nil {
return fmt.Errorf("Error deleting builder service: %v", err)
}
@@ -356,15 +417,15 @@ func (envw *environmentWatcher) deleteBuilderService(sel map[string]string) erro
return nil
}
func (envw *environmentWatcher) deleteBuilderDeployment(sel map[string]string) error {
deployList, err := envw.getBuilderDeploymentList(sel)
func (envw *environmentWatcher) deleteBuilderDeployment(sel map[string]string, ns string) error {
deployList, err := envw.getBuilderDeploymentList(sel, ns)
if err != nil {
return err
}
for _, deploy := range deployList {
log.Printf("Removing builder deployment: %v", deploy.ObjectMeta.Name)
err = envw.kubernetesClient.ExtensionsV1beta1().
Deployments(envw.builderNamespace).
Deployments(ns).
Delete(deploy.ObjectMeta.Name, &delOpt)
if err != nil {
return fmt.Errorf("Error deleteing builder deployment: %v", err)
@@ -373,8 +434,8 @@ func (envw *environmentWatcher) deleteBuilderDeployment(sel map[string]string) e
return nil
}
func (envw *environmentWatcher) getBuilderServiceList(sel map[string]string) ([]apiv1.Service, error) {
svcList, err := envw.kubernetesClient.Services(envw.builderNamespace).List(
func (envw *environmentWatcher) getBuilderServiceList(sel map[string]string, ns string) ([]apiv1.Service, error) {
svcList, err := envw.kubernetesClient.Services(ns).List(
metav1.ListOptions{
LabelSelector: labels.Set(sel).AsSelector().String(),
})
@@ -384,12 +445,12 @@ func (envw *environmentWatcher) getBuilderServiceList(sel map[string]string) ([]
return svcList.Items, nil
}
func (envw *environmentWatcher) createBuilderService(env *crd.Environment) (*apiv1.Service, error) {
name := envw.getCacheKey(env.Metadata.Name, env.Metadata.ResourceVersion)
sel := envw.getLabels(env.Metadata.Name, env.Metadata.ResourceVersion)
func (envw *environmentWatcher) createBuilderService(env *crd.Environment, ns string) (*apiv1.Service, error) {
name := fmt.Sprintf("%v-%v", env.Metadata.Name, env.Metadata.ResourceVersion)
sel := envw.getLabels(env.Metadata.Name, ns, env.Metadata.ResourceVersion)
service := apiv1.Service{
ObjectMeta: metav1.ObjectMeta{
Namespace: envw.builderNamespace,
Namespace: ns,
Name: name,
Labels: sel,
},
@@ -419,15 +480,15 @@ func (envw *environmentWatcher) createBuilderService(env *crd.Environment) (*api
},
}
log.Printf("Creating builder service: %v", name)
_, err := envw.kubernetesClient.Services(envw.builderNamespace).Create(&service)
_, err := envw.kubernetesClient.Services(ns).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.ExtensionsV1beta1().Deployments(envw.builderNamespace).List(
func (envw *environmentWatcher) getBuilderDeploymentList(sel map[string]string, ns string) ([]v1beta1.Deployment, error) {
deployList, err := envw.kubernetesClient.ExtensionsV1beta1().Deployments(ns).List(
metav1.ListOptions{
LabelSelector: labels.Set(sel).AsSelector().String(),
})
@@ -437,12 +498,12 @@ func (envw *environmentWatcher) getBuilderDeploymentList(sel map[string]string)
return deployList.Items, nil
}
func (envw *environmentWatcher) createBuilderDeployment(env *crd.Environment) (*v1beta1.Deployment, error) {
func (envw *environmentWatcher) createBuilderDeployment(env *crd.Environment, ns string) (*v1beta1.Deployment, error) {
sharedMountPath := "/packages"
sharedCfgMapPath := "/configs"
sharedSecretPath := "/secrets"
name := envw.getCacheKey(env.Metadata.Name, env.Metadata.ResourceVersion)
sel := envw.getLabels(env.Metadata.Name, env.Metadata.ResourceVersion)
name := fmt.Sprintf("%v-%v", env.Metadata.Name, env.Metadata.ResourceVersion)
sel := envw.getLabels(env.Metadata.Name, ns, env.Metadata.ResourceVersion)
var replicas int32 = 1
podAnnotation := make(map[string]string)
@@ -452,7 +513,7 @@ func (envw *environmentWatcher) createBuilderDeployment(env *crd.Environment) (*
deployment := &v1beta1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: envw.builderNamespace,
Namespace: ns,
Name: name,
Labels: sel,
},
@@ -565,10 +626,11 @@ func (envw *environmentWatcher) createBuilderDeployment(env *crd.Environment) (*
},
},
}
log.Printf("Creating builder deployment: %v", envw.getCacheKey(env.Metadata.Name, env.Metadata.ResourceVersion))
_, err := envw.kubernetesClient.ExtensionsV1beta1().Deployments(envw.builderNamespace).Create(deployment)
log.Printf("Creating builder deployment: %v", fmt.Sprintf("%v-%v", env.Metadata.Name, env.Metadata.ResourceVersion))
_, err := envw.kubernetesClient.ExtensionsV1beta1().Deployments(ns).Create(deployment)
if err != nil {
return nil, err
}
return deployment, nil
}
+36 -9
View File
@@ -30,26 +30,29 @@ import (
"github.com/fission/fission"
"github.com/fission/fission/cache"
"github.com/fission/fission/crd"
"k8s.io/client-go/kubernetes"
)
type (
packageWatcher struct {
fissionClient *crd.FissionClient
k8sClient *kubernetes.Clientset
podStore k8sCache.Store
pkgStore k8sCache.Store
builderNamespace string
storageSvcUrl string
}
)
func makePackageWatcher(fissionClient *crd.FissionClient, getter k8sCache.Getter,
func makePackageWatcher(fissionClient *crd.FissionClient, k8sClientSet *kubernetes.Clientset,
builderNamespace string, storageSvcUrl string) *packageWatcher {
lw := k8sCache.NewListWatchFromClient(getter, "pods", builderNamespace, fields.Everything())
lw := k8sCache.NewListWatchFromClient(k8sClientSet.CoreV1().RESTClient(), "pods", metav1.NamespaceAll, fields.Everything())
store, controller := k8sCache.NewInformer(lw, &apiv1.Pod{}, 30*time.Second, k8sCache.ResourceEventHandlerFuncs{})
go controller.Run(make(chan struct{}))
pkgw := &packageWatcher{
fissionClient: fissionClient,
k8sClient: k8sClientSet,
podStore: store,
builderNamespace: builderNamespace,
storageSvcUrl: storageSvcUrl,
@@ -118,8 +121,16 @@ func (pkgw *packageWatcher) build(buildCache *cache.Cache, srcpkg *crd.Package)
for _, item := range items {
pod := item.(*apiv1.Pod)
// In order to support backward compatibility, for all builder images created in default env,
// the pods will be created in fission-builder namespace
builderNs := pkgw.builderNamespace
if env.Metadata.Namespace != metav1.NamespaceDefault {
builderNs = env.Metadata.Namespace
}
// Filter non-matching pods
if pod.ObjectMeta.Labels[LABEL_ENV_NAME] != env.Metadata.Name ||
pod.ObjectMeta.Labels[LABEL_ENV_NAMESPACE] != builderNs ||
pod.ObjectMeta.Labels[LABEL_ENV_RESOURCEVERSION] != env.Metadata.ResourceVersion {
continue
}
@@ -138,8 +149,18 @@ func (pkgw *packageWatcher) build(buildCache *cache.Cache, srcpkg *crd.Package)
break
}
uploadResp, buildLogs, err := buildPackage(pkgw.fissionClient,
pkgw.builderNamespace, pkgw.storageSvcUrl, pkg)
// Add the package getter rolebinding to builder sa
// we continue here if role binding was not setup succeesffully. this is because without this, the fetcher wont be able to fetch the source pkg into the container and
// the build will fail eventually
err := fission.SetupRoleBinding(pkgw.k8sClient, fission.PackageGetterRB, pkg.Metadata.Namespace, fission.PackageGetterCR, fission.ClusterRole, fission.FissionBuilderSA, builderNs)
if err != nil {
log.Printf("Error : %v in setting up the role binding %s for pkg : %s.%s", err, fission.PackageGetterRB, pkg.Metadata.Name, pkg.Metadata.Namespace)
continue
} else {
log.Printf("Setup rolebinding for sa : %s.%s for pkg : %s.%s", fission.FissionBuilderSA, builderNs, pkg.Metadata.Name, pkg.Metadata.Namespace)
}
uploadResp, buildLogs, err := buildPackage(pkgw.fissionClient, builderNs, pkgw.storageSvcUrl, pkg)
if err != nil {
log.Printf("Error building package %v: %v", pkg.Metadata.Name, err)
updatePackage(pkgw.fissionClient, pkg, fission.BuildStatusFailed, buildLogs, nil)
@@ -149,7 +170,7 @@ func (pkgw *packageWatcher) build(buildCache *cache.Cache, srcpkg *crd.Package)
log.Printf("Start updating info of package: %v", pkg.Metadata.Name)
fnList, err := pkgw.fissionClient.
Functions(metav1.NamespaceDefault).List(metav1.ListOptions{})
Functions(metav1.NamespaceAll).List(metav1.ListOptions{})
if err != nil {
e := fmt.Sprintf("Error getting function list: %v", err)
log.Println(e)
@@ -191,13 +212,18 @@ func (pkgw *packageWatcher) build(buildCache *cache.Cache, srcpkg *crd.Package)
// build timeout
updatePackage(pkgw.fissionClient, pkg,
fission.BuildStatusFailed, "Build timeout due to environment builder not ready", nil)
log.Printf("Max retries exceeded in building the source pkg : %s.%s, timeout due to environment builder not ready",
pkg.Metadata.Name, pkg.Metadata.Namespace)
return
}
func (pkgw *packageWatcher) watchPackages() {
func (pkgw *packageWatcher) watchPackages(fissionClient *crd.FissionClient,
kubernetesClient *kubernetes.Clientset, builderNamespace string) {
buildCache := cache.MakeCache(0, 0)
lw := k8sCache.NewListWatchFromClient(pkgw.fissionClient.GetCrdClient(), "packages", apiv1.NamespaceDefault, fields.Everything())
_, controller := k8sCache.NewInformer(lw, &crd.Package{}, 60*time.Second, k8sCache.ResourceEventHandlerFuncs{
lw := k8sCache.NewListWatchFromClient(pkgw.fissionClient.GetCrdClient(), "packages", apiv1.NamespaceAll, fields.Everything())
pkgStore, controller := k8sCache.NewInformer(lw, &crd.Package{}, 60*time.Second, k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
pkg := obj.(*crd.Package)
go pkgw.build(buildCache, pkg)
@@ -207,5 +233,6 @@ func (pkgw *packageWatcher) watchPackages() {
go pkgw.build(buildCache, pkg)
},
})
pkgw.pkgStore = pkgStore
controller.Run(make(chan struct{}))
}
+31 -43
View File
@@ -22,6 +22,37 @@ metadata:
istio-injection: enabled
{{- end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: secret-configmap-getter
rules:
- apiGroups:
- '*'
resources:
- secrets
- configmaps
verbs:
- get
- watch
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: package-getter
rules:
- apiGroups:
- '*'
resources:
- packages
verbs:
- get
- watch
- list
---
apiVersion: v1
kind: ServiceAccount
@@ -44,21 +75,6 @@ roleRef:
name: admin
apiGroup: rbac.authorization.k8s.io
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: fission-function-admin
namespace: {{ .Values.functionNamespace }}
subjects:
- kind: ServiceAccount
name: fission-svc
namespace: {{ .Release.Namespace }}
roleRef:
kind: ClusterRole
name: admin
apiGroup: rbac.authorization.k8s.io
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
@@ -80,20 +96,6 @@ metadata:
name: fission-fetcher
namespace: {{ .Values.functionNamespace }}
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: fission-fetcher-crd
subjects:
- kind: ServiceAccount
name: fission-fetcher
namespace: {{ .Values.functionNamespace }}
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: ServiceAccount
@@ -101,20 +103,6 @@ metadata:
name: fission-builder
namespace: {{ .Values.builderNamespace }}
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: fission-builder-crd
subjects:
- kind: ServiceAccount
name: fission-builder
namespace: {{ .Values.builderNamespace }}
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
---
apiVersion: extensions/v1beta1
kind: Deployment
+31 -43
View File
@@ -22,6 +22,37 @@ metadata:
istio-injection: enabled
{{- end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: secret-configmap-getter
rules:
- apiGroups:
- '*'
resources:
- secrets
- configmaps
verbs:
- get
- watch
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: package-getter
rules:
- apiGroups:
- '*'
resources:
- packages
verbs:
- get
- watch
- list
---
apiVersion: v1
kind: ServiceAccount
@@ -44,21 +75,6 @@ roleRef:
name: admin
apiGroup: rbac.authorization.k8s.io
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: fission-function-admin
namespace: {{ .Values.functionNamespace }}
subjects:
- kind: ServiceAccount
name: fission-svc
namespace: {{ .Release.Namespace }}
roleRef:
kind: ClusterRole
name: admin
apiGroup: rbac.authorization.k8s.io
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
@@ -80,20 +96,6 @@ metadata:
name: fission-fetcher
namespace: {{ .Values.functionNamespace }}
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: fission-fetcher-crd
subjects:
- kind: ServiceAccount
name: fission-fetcher
namespace: {{ .Values.functionNamespace }}
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: ServiceAccount
@@ -101,20 +103,6 @@ metadata:
name: fission-builder
namespace: {{ .Values.builderNamespace }}
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: fission-builder-crd
subjects:
- kind: ServiceAccount
name: fission-builder
namespace: {{ .Values.builderNamespace }}
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
---
apiVersion: extensions/v1beta1
kind: Deployment
+5 -1
View File
@@ -28,11 +28,15 @@ import (
"github.com/gorilla/handlers"
"github.com/imdario/mergo"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apiv1 "k8s.io/client-go/pkg/api/v1"
)
func UrlForFunction(name string) string {
func UrlForFunction(name, namespace string) string {
prefix := "/fission-function"
if namespace != metav1.NamespaceDefault {
prefix = fmt.Sprintf("/fission-function/%s", namespace)
}
return fmt.Sprintf("%v/%v", prefix, name)
}
+258
View File
@@ -0,0 +1,258 @@
/*
Copyright 2016 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fission
import (
"log"
"fmt"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/client-go/kubernetes"
apiv1 "k8s.io/client-go/pkg/api/v1"
rbac "k8s.io/client-go/pkg/apis/rbac/v1beta1"
)
// This file has util functions needed for setting up and cleaning up RBAC objects.
const (
MaxRetries = 10
)
// MakeSAObj returns a ServiceAccount object with the given SA name and namespace
func MakeSAObj(sa, ns string) *apiv1.ServiceAccount {
return &apiv1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Namespace: ns,
Name: sa,
},
}
}
// SetupSA checks if a service account is present in the namespace, if not creates it.
func SetupSA(k8sClient *kubernetes.Clientset, sa, ns string) (*apiv1.ServiceAccount, error) {
saObj, err := k8sClient.CoreV1Client.ServiceAccounts(ns).Get(sa, metav1.GetOptions{})
if err == nil {
return saObj, nil
}
if k8serrors.IsNotFound(err) {
saObj = MakeSAObj(sa, ns)
saObj, err = k8sClient.CoreV1Client.ServiceAccounts(ns).Create(saObj)
}
return saObj, err
}
// makeRoleBindingObj is a helper function called from other functions in this file only.
// given a rolebinging name and namespace, it makes a rolebinding object mapping the role to the SA of the namespace.
func makeRoleBindingObj(roleBinding, roleBindingNs, role, roleKind, sa, saNamespace string) *rbac.RoleBinding {
return &rbac.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: roleBinding,
Namespace: roleBindingNs,
},
Subjects: []rbac.Subject{
{
Kind: "ServiceAccount",
Name: sa,
Namespace: saNamespace,
},
},
RoleRef: rbac.RoleRef{
Kind: roleKind,
Name: role,
},
}
}
// isSAInRoleBinding checkis if a service account is present in the rolebinding object
func isSAInRoleBinding(rbObj *rbac.RoleBinding, sa, ns string) bool {
for _, subject := range rbObj.Subjects {
if subject.Name == sa && subject.Namespace == ns {
return true
}
}
return false
}
// PatchSpec is a standard struct needed for JSON merge.
type PatchSpec struct {
Op string `json:"op"`
Path string `json:"path"`
Value rbac.Subject `json:"value"`
}
// AddSaToRoleBindingWithRetries adds a service account to a rolebinding object. IT retries on already exists and conflict errors.
func AddSaToRoleBindingWithRetries(k8sClient *kubernetes.Clientset, roleBinding, roleBindingNs, sa, saNamespace, role, roleKind string) (err error) {
patch := PatchSpec{}
patch.Op = "add"
patch.Path = "/subjects/-"
patch.Value = rbac.Subject{
Kind: "ServiceAccount",
Name: sa,
Namespace: saNamespace,
}
patchJson, err := json.Marshal([]PatchSpec{patch})
if err != nil {
log.Printf("Error marshalling patch into json")
return err
}
for i := 0; i < MaxRetries; i++ {
_, err = k8sClient.RbacV1beta1().RoleBindings(roleBindingNs).Patch(roleBinding, types.JSONPatchType, patchJson)
if err == nil {
log.Printf("Patched rolebinding : %s.%s", roleBinding, roleBindingNs)
return err
}
if k8serrors.IsNotFound(err) {
// someone may have deleted the object between us checking if the object is present and deciding to patch
// so just create the object again
rbObj := makeRoleBindingObj(roleBinding, roleBindingNs, role, roleKind, sa, saNamespace)
rbObj, err = k8sClient.RbacV1beta1().RoleBindings(roleBindingNs).Create(rbObj)
if err == nil {
log.Printf("Created rolebinding : %s.%s", roleBinding, roleBindingNs)
return err
}
if k8serrors.IsAlreadyExists(err) {
log.Printf("rolebinding object : %s.%s already exists, retrying patch, err: %v", roleBinding, roleBindingNs, err)
continue
}
log.Printf("Error returned by rolebinding create, %v", err)
return err
}
if k8serrors.IsConflict(err) {
// TODO : Need to test this out, not able to simulate conflicts yet.
// Initially, my understanding was that patch can never error on conflict because Api server will handle the conflicts for us.
// but one CI run did show patch errored out on conflict : https://api.travis-ci.org/v3/job/373161490/log.txt, look for :
// Error returned by rolebinding patch : <some more text> there is a meaningful conflict (firstResourceVersion: "35482724", currentResourceVersion: "35482849")
// so, m guessing retrying patch should help. will watch out for any such conflicts and fix the issue if any
log.Printf("Conflict reported on patch of rolebinding : %s.%s. Retrying patch operation, err : %v", roleBinding, roleBindingNs, err)
continue
}
log.Printf("Error returned by rolebinding patch : %v", err)
return err
}
log.Printf("Exceeded maxretries : %d adding SA: %s.%s to rolebinding : %s.%s, giving up, err: %v", MaxRetries, sa, saNamespace, roleBinding, roleBindingNs, err)
return err
}
// RemoveSAFromRoleBindingWithRetries removes an SA from the rolebinding passed as parameter. If this is the only SA in
// the rolebinding, then it deletes the rolebinding object.
func RemoveSAFromRoleBindingWithRetries(k8sClient *kubernetes.Clientset, roleBinding, roleBindingNs string, saToRemove map[string]bool) (err error) {
for i := 0; i < MaxRetries; i++ {
rbObj, err := k8sClient.RbacV1beta1().RoleBindings(roleBindingNs).Get(
roleBinding, metav1.GetOptions{})
if err != nil {
// silently ignoring the error. there's no need for us to remove sa anymore.
log.Printf("rolebinding %s.%s not found, but ignoring the error since we're cleaning up", roleBinding, roleBindingNs)
return nil
}
subjects := rbObj.Subjects
newSubjects := make([]rbac.Subject, 0)
// TODO : optimize it.
for _, item := range subjects {
if _, ok := saToRemove[MakeSAMapKey(item.Name, item.Namespace)]; ok {
continue
}
newSubjects = append(newSubjects, rbac.Subject{
Kind: "ServiceAccount",
Name: item.Name,
Namespace: item.Namespace,
})
}
if len(newSubjects) == 0 {
return DeleteRoleBinding(k8sClient, roleBinding, roleBindingNs)
}
rbObj.Subjects = newSubjects
// cant use patch for deletes, the results become in-deterministic, so using update.
_, err = k8sClient.RbacV1beta1().RoleBindings(rbObj.Namespace).Update(rbObj)
switch {
case err == nil:
log.Printf("Removed sa's : %v from rolebinding : %s.%s", saToRemove, roleBinding, roleBindingNs)
return nil
case k8serrors.IsConflict(err):
log.Printf("Conflict in update of rolebinding: %s.%s, retrying", roleBinding, roleBindingNs)
continue
default:
log.Printf("Rolebinding Update Errored out : %v", err)
return err
}
}
log.Printf("Maxretries : %d exceeded for removing SA's : %v from rolebinding %s.%s, giving up, err: %v", MaxRetries, saToRemove, roleBinding, roleBindingNs, err)
return err
}
// SetupRoleBinding adds a role to a service account if the rolebinding object is already present in the namespace.
// if not, it creates a rolebinding object granting the role to the SA in the namespace.
func SetupRoleBinding(k8sClient *kubernetes.Clientset, roleBinding, roleBindingNs, role, roleKind, sa, saNamespace string) error {
// get the role binding object
rbObj, err := k8sClient.RbacV1beta1().RoleBindings(roleBindingNs).Get(
roleBinding, metav1.GetOptions{})
if err == nil {
if !isSAInRoleBinding(rbObj, sa, saNamespace) {
return AddSaToRoleBindingWithRetries(k8sClient, roleBinding, roleBindingNs, sa, saNamespace, role, roleKind)
}
log.Printf("SA : %s.%s already present in rolebinding : %s.%s, so nothing to add", sa, saNamespace, roleBinding, roleBindingNs)
return nil
}
// if role binding is missing, create it. also add this sa to the binding.
if k8serrors.IsNotFound(err) {
log.Printf("Rolebinding %s does NOT exist in ns %s. Creating it", roleBinding, roleBindingNs)
rbObj = makeRoleBindingObj(roleBinding, roleBindingNs, role, roleKind, sa, saNamespace)
rbObj, err = k8sClient.RbacV1beta1().RoleBindings(roleBindingNs).Create(rbObj)
if k8serrors.IsAlreadyExists(err) {
err = AddSaToRoleBindingWithRetries(k8sClient, roleBinding, roleBindingNs, sa, saNamespace, role, roleKind)
}
}
return err
}
// DeleteRoleBinding deletes a rolebinding object. if k8s throws an error that the rolebinding is not there, it just
// returns silently.
func DeleteRoleBinding(k8sClient *kubernetes.Clientset, roleBinding, roleBindingNs string) error {
// if deleteRoleBinding is invoked by 2 fission services at the same time for the same rolebinding,
// the first call will succeed while the 2nd will fail with isNotFound. but we dont want to error out then.
err := k8sClient.RbacV1beta1().RoleBindings(roleBindingNs).Delete(roleBinding, &metav1.DeleteOptions{})
if err == nil || k8serrors.IsNotFound(err) {
return nil
}
return err
}
func MakeSAMapKey(saName, saNamespace string) string {
return fmt.Sprintf("%s-%s", saName, saNamespace)
}
+30
View File
@@ -26,7 +26,9 @@ import (
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
apiv1 "k8s.io/client-go/pkg/api/v1"
"github.com/fission/fission"
"github.com/fission/fission/crd"
@@ -109,6 +111,31 @@ func (api *API) respondWithError(w http.ResponseWriter, err error) {
http.Error(w, msg, code)
}
func (api *API) extractQueryParamFromRequest(r *http.Request, queryParam string) string {
values := r.URL.Query()
return values.Get(queryParam)
}
// check if namespace exists, if not create it.
func (api *API) createNsIfNotExists(ns string) error {
if ns == metav1.NamespaceDefault {
// we dont have to create default ns
return nil
}
_, err := api.kubernetesClient.CoreV1Client.Namespaces().Get(ns, metav1.GetOptions{})
if err != nil && kerrors.IsNotFound(err) {
ns := &apiv1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: ns,
},
}
_, err = api.kubernetesClient.CoreV1Client.Namespaces().Create(ns)
}
return err
}
func (api *API) getLogDBConfig(dbType string) logDBConfig {
dbType = strings.ToUpper(dbType)
// retrieve db auth config from the env
@@ -191,6 +218,9 @@ func (api *API) Serve(port int) {
r.HandleFunc("/v2/deleteTpr", api.Tpr2crdApi).Methods("DELETE")
r.HandleFunc("/v2/secrets/{secret}", api.SecretGet).Methods("GET")
r.HandleFunc("/v2/configmaps/{configmap}", api.ConfigMapGet).Methods("GET")
r.HandleFunc("/proxy/{dbType}", api.FunctionLogsApiPost).Methods("POST")
r.HandleFunc("/proxy/storage/v1/archive", api.StorageServiceProxy)
r.HandleFunc("/proxy/logs/{function}", api.FunctionPodLogs).Methods("POST")
+5 -5
View File
@@ -122,7 +122,7 @@ func TestFunctionApi(t *testing.T) {
panicIf(err)
defer g.client.FunctionDelete(m2)
funcs, err := g.client.FunctionList()
funcs, err := g.client.FunctionList(metav1.NamespaceDefault)
panicIf(err)
assert(len(funcs) == 2, fmt.Sprintf("created two functions, but found %v", len(funcs)))
@@ -188,7 +188,7 @@ func TestHTTPTriggerApi(t *testing.T) {
panicIf(err)
defer g.client.HTTPTriggerDelete(m2)
ts, err := g.client.HTTPTriggerList()
ts, err := g.client.HTTPTriggerList(metav1.NamespaceDefault)
panicIf(err)
assert(len(ts) == 2, fmt.Sprintf("created two triggers, but found %v", len(ts)))
}
@@ -235,7 +235,7 @@ func TestEnvironmentApi(t *testing.T) {
panicIf(err)
defer g.client.EnvironmentDelete(m2)
ts, err := g.client.EnvironmentList()
ts, err := g.client.EnvironmentList(metav1.NamespaceDefault)
panicIf(err)
assert(len(ts) == 2, fmt.Sprintf("created two envs, but found %v", len(ts)))
}
@@ -279,7 +279,7 @@ func TestWatchApi(t *testing.T) {
panicIf(err)
defer g.client.WatchDelete(m2)
ws, err := g.client.WatchList()
ws, err := g.client.WatchList(metav1.NamespaceDefault)
panicIf(err)
assert(len(ws) == 2, fmt.Sprintf("created two watches, but found %v", len(ws)))
}
@@ -323,7 +323,7 @@ func TestTimeTriggerApi(t *testing.T) {
_, err = g.client.TimeTriggerCreate(testTrigger)
assertCronSpecFails(err)
ts, err := g.client.TimeTriggerList()
ts, err := g.client.TimeTriggerList(metav1.NamespaceDefault)
panicIf(err)
assert(len(ts) == 1, fmt.Sprintf("created two time triggers, but found %v", len(ts)))
}
+74
View File
@@ -0,0 +1,74 @@
/*
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 client
import (
"encoding/json"
"fmt"
"net/http"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apiv1 "k8s.io/client-go/pkg/api/v1"
)
func (c *Client) SecretGet(m *metav1.ObjectMeta) (*apiv1.Secret, error) {
relativeUrl := fmt.Sprintf("secrets/%v", m.Name)
relativeUrl += fmt.Sprintf("?namespace=%v", m.Namespace)
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var secret apiv1.Secret
err = json.Unmarshal(body, &secret)
if err != nil {
return nil, err
}
return &secret, nil
}
func (c *Client) ConfigMapGet(m *metav1.ObjectMeta) (*apiv1.ConfigMap, error) {
relativeUrl := fmt.Sprintf("configmaps/%v", m.Name)
relativeUrl += fmt.Sprintf("?namespace=%v", m.Namespace)
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var configMap apiv1.ConfigMap
err = json.Unmarshal(body, &configMap)
if err != nil {
return nil, err
}
return &configMap, nil
}
+3 -2
View File
@@ -121,8 +121,9 @@ func (c *Client) EnvironmentDelete(m *metav1.ObjectMeta) error {
return c.delete(relativeUrl)
}
func (c *Client) EnvironmentList() ([]crd.Environment, error) {
resp, err := http.Get(c.url("environments"))
func (c *Client) EnvironmentList(ns string) ([]crd.Environment, error) {
relativeUrl := fmt.Sprintf("environments?namespace=%v", ns)
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
+3 -2
View File
@@ -134,8 +134,9 @@ func (c *Client) FunctionDelete(m *metav1.ObjectMeta) error {
return c.delete(relativeUrl)
}
func (c *Client) FunctionList() ([]crd.Function, error) {
resp, err := http.Get(c.url("functions"))
func (c *Client) FunctionList(functionNamespace string) ([]crd.Function, error) {
relativeUrl := fmt.Sprintf("functions?namespace=%v", functionNamespace)
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
+3 -2
View File
@@ -120,8 +120,9 @@ func (c *Client) HTTPTriggerDelete(m *metav1.ObjectMeta) error {
return c.delete(relativeUrl)
}
func (c *Client) HTTPTriggerList() ([]crd.HTTPTrigger, error) {
resp, err := http.Get(c.url("triggers/http"))
func (c *Client) HTTPTriggerList(triggerNamespace string) ([]crd.HTTPTrigger, error) {
relativeUrl := fmt.Sprintf("triggers/http?namespace=%v", triggerNamespace)
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
+3 -2
View File
@@ -94,8 +94,9 @@ func (c *Client) WatchDelete(m *metav1.ObjectMeta) error {
return c.delete(relativeUrl)
}
func (c *Client) WatchList() ([]crd.KubernetesWatchTrigger, error) {
resp, err := http.Get(c.url("watches"))
func (c *Client) WatchList(ns string) ([]crd.KubernetesWatchTrigger, error) {
relativeUrl := fmt.Sprintf("watches?namespace=%v", ns)
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
+2 -2
View File
@@ -120,11 +120,11 @@ func (c *Client) MessageQueueTriggerDelete(m *metav1.ObjectMeta) error {
return c.delete(relativeUrl)
}
func (c *Client) MessageQueueTriggerList(mqType string) ([]crd.MessageQueueTrigger, error) {
func (c *Client) MessageQueueTriggerList(mqType string, ns string) ([]crd.MessageQueueTrigger, error) {
relativeUrl := "triggers/messagequeue"
if len(mqType) > 0 {
// TODO remove this, replace with field selector
relativeUrl += fmt.Sprintf("?mqtype=%v", mqType)
relativeUrl += fmt.Sprintf("?mqtype=%v&namespace=%v", mqType, ns)
}
resp, err := http.Get(c.url(relativeUrl))
+3 -2
View File
@@ -120,8 +120,9 @@ func (c *Client) PackageDelete(m *metav1.ObjectMeta) error {
return c.delete(relativeUrl)
}
func (c *Client) PackageList() ([]crd.Package, error) {
resp, err := http.Get(c.url("packages"))
func (c *Client) PackageList(pkgNamespace string) ([]crd.Package, error) {
relativeUrl := fmt.Sprintf("packages?namespace=%v", pkgNamespace)
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
+3 -2
View File
@@ -120,8 +120,9 @@ func (c *Client) TimeTriggerDelete(m *metav1.ObjectMeta) error {
return c.delete(relativeUrl)
}
func (c *Client) TimeTriggerList() ([]crd.TimeTrigger, error) {
resp, err := http.Get(c.url("triggers/time"))
func (c *Client) TimeTriggerList(ns string) ([]crd.TimeTrigger, error) {
relativeUrl := fmt.Sprintf("triggers/time?namespace=%v", ns)
resp, err := http.Get(c.url(relativeUrl))
if err != nil {
return nil, err
}
+49
View File
@@ -0,0 +1,49 @@
/*
Copyright 2016 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"encoding/json"
"net/http"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (a *API) ConfigMapGet(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["configmap"]
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceDefault
}
configMap, err := a.kubernetesClient.ConfigMaps(ns).Get(name, metav1.GetOptions{})
if err != nil {
log.Printf("Error getting config map: %s from ns: %s", name, ns)
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(configMap)
if err != nil {
a.respondWithError(w, err)
return
}
a.respondWithSuccess(w, resp)
}
+17 -3
View File
@@ -30,7 +30,12 @@ import (
)
func (a *API) EnvironmentApiList(w http.ResponseWriter, r *http.Request) {
envs, err := a.fissionClient.Environments(metav1.NamespaceAll).List(metav1.ListOptions{})
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceAll
}
envs, err := a.fissionClient.Environments(ns).List(metav1.ListOptions{})
if err != nil {
a.respondWithError(w, err)
return
@@ -60,6 +65,13 @@ func (a *API) EnvironmentApiCreate(w http.ResponseWriter, r *http.Request) {
return
}
// check if namespace exists, if not create it.
err = a.createNsIfNotExists(env.Metadata.Namespace)
if err != nil {
a.respondWithError(w, err)
return
}
enew, err := a.fissionClient.Environments(env.Metadata.Namespace).Create(&env)
if err != nil {
a.respondWithError(w, err)
@@ -79,7 +91,8 @@ func (a *API) EnvironmentApiCreate(w http.ResponseWriter, r *http.Request) {
func (a *API) EnvironmentApiGet(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["environment"]
ns := vars["namespace"]
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceDefault
}
@@ -140,7 +153,8 @@ func (a *API) EnvironmentApiUpdate(w http.ResponseWriter, r *http.Request) {
func (a *API) EnvironmentApiDelete(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["environment"]
ns := vars["namespace"]
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceDefault
}
+16 -5
View File
@@ -29,7 +29,6 @@ import (
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/pkg/api"
apiv1 "k8s.io/client-go/pkg/api/v1"
restclient "k8s.io/client-go/rest"
@@ -44,7 +43,12 @@ func (a *API) getIstioServiceLabels(fnName string) map[string]string {
}
func (a *API) FunctionApiList(w http.ResponseWriter, r *http.Request) {
funcs, err := a.fissionClient.Functions(metav1.NamespaceAll).List(metav1.ListOptions{})
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceAll
}
funcs, err := a.fissionClient.Functions(ns).List(metav1.ListOptions{})
if err != nil {
a.respondWithError(w, err)
return
@@ -73,6 +77,13 @@ func (a *API) FunctionApiCreate(w http.ResponseWriter, r *http.Request) {
return
}
// check if namespace exists, if not create it.
err = a.createNsIfNotExists(f.Metadata.Namespace)
if err != nil {
a.respondWithError(w, err)
return
}
fnew, err := a.fissionClient.Functions(f.Metadata.Namespace).Create(&f)
if err != nil {
a.respondWithError(w, err)
@@ -92,7 +103,7 @@ func (a *API) FunctionApiCreate(w http.ResponseWriter, r *http.Request) {
func (a *API) FunctionApiGet(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["function"]
ns := vars["namespace"]
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceDefault
}
@@ -151,7 +162,7 @@ func (a *API) FunctionApiUpdate(w http.ResponseWriter, r *http.Request) {
func (a *API) FunctionApiDelete(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["function"]
ns := vars["namespace"]
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceDefault
}
@@ -205,7 +216,7 @@ func (a *API) FunctionPodLogs(w http.ResponseWriter, r *http.Request) {
ns = "fission-function"
}
f, err := a.fissionClient.Functions(api.NamespaceDefault).Get(fnName)
f, err := a.fissionClient.Functions(ns).Get(fnName)
if err != nil {
a.respondWithError(w, err)
return
+15 -3
View File
@@ -30,7 +30,12 @@ import (
)
func (a *API) HTTPTriggerApiList(w http.ResponseWriter, r *http.Request) {
triggers, err := a.fissionClient.HTTPTriggers(metav1.NamespaceAll).List(metav1.ListOptions{})
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceAll
}
triggers, err := a.fissionClient.HTTPTriggers(ns).List(metav1.ListOptions{})
if err != nil {
a.respondWithError(w, err)
return
@@ -81,6 +86,13 @@ func (a *API) HTTPTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
return
}
// check if namespace exists, if not create it.
err = a.createNsIfNotExists(t.Metadata.Namespace)
if err != nil {
a.respondWithError(w, err)
return
}
tnew, err := a.fissionClient.HTTPTriggers(t.Metadata.Namespace).Create(&t)
if err != nil {
a.respondWithError(w, err)
@@ -100,7 +112,7 @@ func (a *API) HTTPTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
func (a *API) HTTPTriggerApiGet(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["httpTrigger"]
ns := vars["namespace"]
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceDefault
}
@@ -166,7 +178,7 @@ func (a *API) HTTPTriggerApiUpdate(w http.ResponseWriter, r *http.Request) {
func (a *API) HTTPTriggerApiDelete(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["httpTrigger"]
ns := vars["namespace"]
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceDefault
}
+15 -3
View File
@@ -30,7 +30,12 @@ import (
func (a *API) MessageQueueTriggerApiList(w http.ResponseWriter, r *http.Request) {
//mqType := r.FormValue("mqtype") // ignored for now
triggers, err := a.fissionClient.MessageQueueTriggers(metav1.NamespaceAll).List(metav1.ListOptions{})
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceAll
}
triggers, err := a.fissionClient.MessageQueueTriggers(ns).List(metav1.ListOptions{})
if err != nil {
a.respondWithError(w, err)
return
@@ -57,6 +62,13 @@ func (a *API) MessageQueueTriggerApiCreate(w http.ResponseWriter, r *http.Reques
return
}
// check if namespace exists, if not create it.
err = a.createNsIfNotExists(mqTrigger.Metadata.Namespace)
if err != nil {
a.respondWithError(w, err)
return
}
tnew, err := a.fissionClient.MessageQueueTriggers(mqTrigger.Metadata.Namespace).Create(&mqTrigger)
if err != nil {
a.respondWithError(w, err)
@@ -75,7 +87,7 @@ func (a *API) MessageQueueTriggerApiCreate(w http.ResponseWriter, r *http.Reques
func (a *API) MessageQueueTriggerApiGet(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["mqTrigger"]
ns := vars["namespace"]
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceDefault
}
@@ -133,7 +145,7 @@ func (a *API) MessageQueueTriggerApiUpdate(w http.ResponseWriter, r *http.Reques
func (a *API) MessageQueueTriggerApiDelete(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["mqTrigger"]
ns := vars["namespace"]
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceDefault
}
+14 -3
View File
@@ -29,7 +29,11 @@ import (
)
func (a *API) PackageApiList(w http.ResponseWriter, r *http.Request) {
funcs, err := a.fissionClient.Packages(metav1.NamespaceAll).List(metav1.ListOptions{})
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceAll
}
funcs, err := a.fissionClient.Packages(ns).List(metav1.ListOptions{})
if err != nil {
a.respondWithError(w, err)
return
@@ -70,6 +74,13 @@ func (a *API) PackageApiCreate(w http.ResponseWriter, r *http.Request) {
return
}
// check if namespace exists, if not create it.
err = a.createNsIfNotExists(f.Metadata.Namespace)
if err != nil {
a.respondWithError(w, err)
return
}
fnew, err := a.fissionClient.Packages(f.Metadata.Namespace).Create(&f)
if err != nil {
a.respondWithError(w, err)
@@ -89,7 +100,7 @@ func (a *API) PackageApiCreate(w http.ResponseWriter, r *http.Request) {
func (a *API) PackageApiGet(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["package"]
ns := vars["namespace"]
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceDefault
}
@@ -154,7 +165,7 @@ func (a *API) PackageApiUpdate(w http.ResponseWriter, r *http.Request) {
func (a *API) PackageApiDelete(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["package"]
ns := vars["namespace"]
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceDefault
}
+49
View File
@@ -0,0 +1,49 @@
/*
Copyright 2016 The Fission Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"encoding/json"
"net/http"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (a *API) SecretGet(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["secret"]
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceDefault
}
secret, err := a.kubernetesClient.Secrets(ns).Get(name, metav1.GetOptions{})
if err != nil {
log.Printf("Error getting secret: %s from ns: %s", name, ns)
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(secret)
if err != nil {
a.respondWithError(w, err)
return
}
a.respondWithSuccess(w, resp)
}
+15 -3
View File
@@ -30,7 +30,12 @@ import (
)
func (a *API) TimeTriggerApiList(w http.ResponseWriter, r *http.Request) {
triggers, err := a.fissionClient.TimeTriggers(metav1.NamespaceAll).List(metav1.ListOptions{})
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceAll
}
triggers, err := a.fissionClient.TimeTriggers(ns).List(metav1.ListOptions{})
if err != nil {
a.respondWithError(w, err)
return
@@ -67,6 +72,13 @@ func (a *API) TimeTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
return
}
// check if namespace exists, if not create it.
err = a.createNsIfNotExists(t.Metadata.Namespace)
if err != nil {
a.respondWithError(w, err)
return
}
tnew, err := a.fissionClient.TimeTriggers(t.Metadata.Namespace).Create(&t)
if err != nil {
a.respondWithError(w, err)
@@ -86,7 +98,7 @@ func (a *API) TimeTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
func (a *API) TimeTriggerApiGet(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["timeTrigger"]
ns := vars["namespace"]
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceDefault
}
@@ -153,7 +165,7 @@ func (a *API) TimeTriggerApiUpdate(w http.ResponseWriter, r *http.Request) {
func (a *API) TimeTriggerApiDelete(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["timeTrigger"]
ns := vars["namespace"]
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceDefault
}
+15 -3
View File
@@ -29,7 +29,12 @@ import (
)
func (a *API) WatchApiList(w http.ResponseWriter, r *http.Request) {
watches, err := a.fissionClient.KubernetesWatchTriggers(metav1.NamespaceAll).List(metav1.ListOptions{})
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceAll
}
watches, err := a.fissionClient.KubernetesWatchTriggers(ns).List(metav1.ListOptions{})
if err != nil {
a.respondWithError(w, err)
return
@@ -59,6 +64,13 @@ func (a *API) WatchApiCreate(w http.ResponseWriter, r *http.Request) {
}
// TODO check for duplicate watches
// TODO check for duplicate watches -> we probably wont need it?
// check if namespace exists, if not create it.
err = a.createNsIfNotExists(watch.Metadata.Namespace)
if err != nil {
a.respondWithError(w, err)
return
}
wnew, err := a.fissionClient.KubernetesWatchTriggers(watch.Metadata.Namespace).Create(&watch)
if err != nil {
@@ -79,7 +91,7 @@ func (a *API) WatchApiCreate(w http.ResponseWriter, r *http.Request) {
func (a *API) WatchApiGet(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["watch"]
ns := vars["namespace"]
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceDefault
}
@@ -107,7 +119,7 @@ func (a *API) WatchApiUpdate(w http.ResponseWriter, r *http.Request) {
func (a *API) WatchApiDelete(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["watch"]
ns := vars["namespace"]
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceDefault
}
+132
View File
@@ -263,3 +263,135 @@ func logErr(msg string, err error) {
log.Printf("Error %v: %v", msg, err)
}
}
// cleanupRoleBindings periodically lists rolebindings across all namespaces and removes Service Accounts from them or
// deletes the rolebindings completely if there are no Service Accounts in a rolebinding object.
func cleanupRoleBindings(client *kubernetes.Clientset, fissionClient *crd.FissionClient, functionNs, envBuilderNs string, cleanupRoleBindingInterval time.Duration) {
for {
log.Println("Starting cleanupRoleBindings cycle")
// get all rolebindings ( just to be efficient, one call to kubernetes )
rbList, err := client.RbacV1beta1().RoleBindings(meta_v1.NamespaceAll).List(meta_v1.ListOptions{})
if err != nil {
// something wrong, but next iteration hopefully succeeds
log.Printf("Error listing rolebindings in all ns, err: %v", err)
continue
}
// go through each role-binding object and do the cleanup necessary
for _, roleBinding := range rbList.Items {
// ignore role-bindings in kube-system namespace
if roleBinding.Namespace == "kube-system" {
continue
}
// ignore role-bindings not created by fission
if roleBinding.Name != fission.PackageGetterRB && roleBinding.Name != fission.SecretConfigMapGetterRB {
continue
}
// in order to find out if there are any functions that need this role-binding in role-binding namespace,
// we can list the functions once per role-binding.
funcList, err := fissionClient.Functions(roleBinding.Namespace).List(meta_v1.ListOptions{})
if err != nil {
log.Printf("Error fetching environment list in : %s", roleBinding.Namespace)
continue
}
// final map of service accounts that can be removed from this roleBinding object
// using a map here instead of a list so the code in RemoveSAFromRoleBindingWithRetries is efficient.
saToRemove := make(map[string]bool)
// the following flags are needed to decide if any of the service accounts can be removed from role-bindings depending on the functions that need them.
// ndmFunc denotes if there's at least one function that has executor type New deploy Manager
// funcEnvReference denotes if there's at least one function that has reference to an environment in the SA Namespace for the SA in question
var ndmFunc, funcEnvReference bool
// iterate through each subject in the role-binding and check if there are any references to them
for _, subj := range roleBinding.Subjects {
ndmFunc = false
funcEnvReference = false
// this is the reverse of what we're doing in setting up of role-bindings. if objects are created in default ns,
// the SA namespace will have the value of "fission-function"/"fission-builder" depending on the SA.
// so now we need to look for the objects in default namespace.
saNs := subj.Namespace
if subj.Namespace == functionNs ||
subj.Namespace == envBuilderNs {
saNs = meta_v1.NamespaceDefault
}
// go through each function and find out if there's either at least one function with env reference in the same namespace as the Service Account in this iteration
// or at least one function using ndm executor in the role-binding namespace and set the corresponding flags
for _, fn := range funcList.Items {
if fn.Spec.Environment.Namespace == saNs {
funcEnvReference = true
break
}
if fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType == fission.ExecutorTypeNewdeploy {
ndmFunc = true
break
}
}
// if its a package-getterr-rb, we have 2 kinds of SAs and each of them is handled differently
// else if its a secret-configmap-rb, we have only one SA which is fission-fetcher
if roleBinding.Name == fission.PackageGetterRB {
// check if there is an env obj in saNs
envList, err := fissionClient.Environments(saNs).List(meta_v1.ListOptions{})
if err != nil {
log.Printf("Error fetching environment list in : %s", saNs)
continue
}
// if the SA in this iteration is fission-builder, then we need to only check
// if either there's at least one env object in the SA's namespace, or,
// if there's at least one function in the role-binding namespace with env reference
// to the SA's namespace.
// if neither, then we can remove this SA from this role-binding
if subj.Name == fission.FissionBuilderSA {
if len(envList.Items) == 0 && !funcEnvReference {
saToRemove[fission.MakeSAMapKey(subj.Name, subj.Namespace)] = true
}
}
// if the SA in this iteration is fission-fetcher, then in addition to above checks,
// we also need to check if there's at least one function with executor type New deploy
// in the rolebinding's namespace.
// if none of them are true, then remove this SA from this role-binding
if subj.Name == fission.FissionFetcherSA {
if len(envList.Items) == 0 && !ndmFunc && !funcEnvReference {
// remove SA from rolebinding
saToRemove[fission.MakeSAMapKey(subj.Name, subj.Namespace)] = true
}
}
} else if roleBinding.Name == fission.SecretConfigMapGetterRB {
// if there's not even one function in the role-binding's namespace and there's not even
// one function with env reference to the SA's namespace, then remove that SA
// from this role-binding
if !ndmFunc && !funcEnvReference {
saToRemove[fission.MakeSAMapKey(subj.Name, subj.Namespace)] = true
}
}
}
// finally, make a call to RemoveSAFromRoleBindingWithRetries for all the service accounts that need to be removed
// for the role-binding in this iteration
if len(saToRemove) != 0 {
log.Printf("saToRemove : %v for rolebinding : %s.%s", saToRemove, roleBinding.Name, roleBinding.Namespace)
// call this once in the end for each role-binding
err = fission.RemoveSAFromRoleBindingWithRetries(client, roleBinding.Name, roleBinding.Namespace, saToRemove)
if err != nil {
// if there's an error, we just log it and proceed with the next role-binding, hoping that this role-binding
// will be processed in next iteration.
log.Printf("Error removing SA : %v from rolebinding : %s.%s, err: %v", saToRemove, roleBinding.Name,
roleBinding.Namespace, err)
}
}
}
// some sleep before the next cleanup iteration
time.Sleep(cleanupRoleBindingInterval)
}
}
+5 -4
View File
@@ -70,6 +70,7 @@ func MakeExecutor(gpm *poolmgr.GenericPoolManager, ndm *newdeploy.NewDeploy, fis
fsCreateWg: make(map[string]*sync.WaitGroup),
}
go executor.serveCreateFuncServices()
return executor
}
@@ -127,7 +128,6 @@ func (executor *Executor) getFunctionExecutorType(meta *metav1.ObjectMeta) (fiss
return "", err
}
return fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType, nil
}
func (executor *Executor) createServiceForFunction(meta *metav1.ObjectMeta) (*fscache.FuncSvc, error) {
@@ -211,9 +211,9 @@ func serveMetric() {
log.Fatal(http.ListenAndServe(metricAddr, nil))
}
// StartExecutor Starts executor and the backend components that executor uses such as Poolmgr,
// deploymgr and potential future backends
func StartExecutor(fissionNamespace string, functionNamespace string, port int) error {
// StartExecutor Starts executor and the executor components such as Poolmgr,
// deploymgr and potential future executor types
func StartExecutor(fissionNamespace string, functionNamespace string, envBuilderNamespace string, port int) error {
// setup a signal handler for SIGTERM
fission.SetupStackTraceHandler()
@@ -235,6 +235,7 @@ func StartExecutor(fissionNamespace string, functionNamespace string, port int)
poolID := strings.ToLower(uniuri.NewLen(8))
cleanupObjects(kubernetesClient, functionNamespace, poolID)
go idleObjectReaper(kubernetesClient, fissionClient, fsCache, time.Minute*2)
go cleanupRoleBindings(kubernetesClient, fissionClient, functionNamespace, envBuilderNamespace, time.Minute*30)
gpm := poolmgr.MakeGenericPoolManager(
fissionClient, kubernetesClient,
+49 -17
View File
@@ -50,14 +50,14 @@ const (
)
func (deploy *NewDeploy) createOrGetDeployment(fn *crd.Function, env *crd.Environment,
deployName string, deployLabels map[string]string) (*v1beta1.Deployment, error) {
deployName string, deployLabels map[string]string, deployNamespace string) (*v1beta1.Deployment, error) {
replicas := int32(fn.Spec.InvokeStrategy.ExecutionStrategy.MinScale)
if replicas == 0 {
replicas = 1
}
existingDepl, err := deploy.kubernetesClient.ExtensionsV1beta1().Deployments(deploy.namespace).Get(deployName, metav1.GetOptions{})
existingDepl, err := deploy.kubernetesClient.ExtensionsV1beta1().Deployments(deployNamespace).Get(deployName, metav1.GetOptions{})
if err == nil {
if existingDepl.Status.ReadyReplicas < replicas {
existingDepl, err = deploy.waitForDeploy(existingDepl, replicas)
@@ -66,13 +66,17 @@ func (deploy *NewDeploy) createOrGetDeployment(fn *crd.Function, env *crd.Enviro
}
if err != nil && k8s_err.IsNotFound(err) {
err := deploy.setupRBACObjs(deployNamespace, fn)
if err != nil {
return nil, err
}
deployment, err := deploy.getDeploymentSpec(fn, env, deployName, deployLabels)
if err != nil {
return nil, err
}
depl, err := deploy.kubernetesClient.ExtensionsV1beta1().Deployments(deploy.namespace).Create(deployment)
depl, err := deploy.kubernetesClient.ExtensionsV1beta1().Deployments(deployNamespace).Create(deployment)
if err != nil {
log.Printf("Error while creating deployment: %v", err)
return nil, err
@@ -85,13 +89,39 @@ func (deploy *NewDeploy) createOrGetDeployment(fn *crd.Function, env *crd.Enviro
}
func (deploy *NewDeploy) getDeployment(fn *crd.Function) (*v1beta1.Deployment, error) {
deployName := deploy.getObjName(fn)
return deploy.kubernetesClient.ExtensionsV1beta1().Deployments(deploy.namespace).Get(deployName, metav1.GetOptions{})
func (deploy *NewDeploy) setupRBACObjs(deployNamespace string, fn *crd.Function) error {
// create fetcher SA in this ns, if not already created
_, err := fission.SetupSA(deploy.kubernetesClient, fission.FissionFetcherSA, deployNamespace)
if err != nil {
log.Printf("Error : %v creating %s in ns : %s for function: %s.%s", err, fission.FissionFetcherSA, deployNamespace, fn.Metadata.Name, fn.Metadata.Namespace)
return err
}
// create a cluster role binding for the fetcher SA, if not already created, granting access to do a get on packages in any ns
err = fission.SetupRoleBinding(deploy.kubernetesClient, fission.PackageGetterRB, fn.Spec.Package.PackageRef.Namespace, fission.PackageGetterCR, fission.ClusterRole, fission.FissionFetcherSA, deployNamespace)
if err != nil {
log.Printf("Error : %v creating %s RoleBinding for function: %s.%s", err, fission.PackageGetterRB, fn.Metadata.Name, fn.Metadata.Namespace)
return err
}
// create rolebinding in function namespace for fetcherSA.envNamespace to be able to get secrets and configmaps
err = fission.SetupRoleBinding(deploy.kubernetesClient, fission.SecretConfigMapGetterRB, fn.Metadata.Namespace, fission.SecretConfigMapGetterCR, fission.ClusterRole, fission.FissionFetcherSA, deployNamespace)
if err != nil {
log.Printf("Error : %v creating %s RoleBinding for function %s.%s", err, fission.SecretConfigMapGetterRB, fn.Metadata.Name, fn.Metadata.Namespace)
return err
}
log.Printf("Set up all RBAC objects for function : %s.%s", fn.Metadata.Name, fn.Metadata.Namespace)
return nil
}
func (deploy *NewDeploy) updateDeployment(deployment *v1beta1.Deployment) error {
_, err := deploy.kubernetesClient.ExtensionsV1beta1().Deployments(deploy.namespace).Update(deployment)
func (deploy *NewDeploy) getDeployment(fn *crd.Function) (*v1beta1.Deployment, error) {
deployName := deploy.getObjName(fn)
return deploy.kubernetesClient.ExtensionsV1beta1().Deployments(fn.Metadata.Namespace).Get(deployName, metav1.GetOptions{})
}
func (deploy *NewDeploy) updateDeployment(deployment *v1beta1.Deployment, ns string) error {
_, err := deploy.kubernetesClient.ExtensionsV1beta1().Deployments(ns).Update(deployment)
return err
}
@@ -115,7 +145,9 @@ func (deploy *NewDeploy) getDeploymentSpec(fn *crd.Function, env *crd.Environmen
if replicas == 0 {
replicas = 1
}
targetFilename := "user"
gracePeriodSeconds := int64(6 * 60)
if env.Spec.TerminationGracePeriod > 0 {
gracePeriodSeconds = env.Spec.TerminationGracePeriod
@@ -351,7 +383,7 @@ func (deploy *NewDeploy) createOrGetHpa(hpaName string, execStrategy *fission.Ex
maxRepl := int32(execStrategy.MaxScale)
targetCPU := int32(execStrategy.TargetCPUPercent)
existingHpa, err := deploy.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(deploy.namespace).Get(hpaName, metav1.GetOptions{})
existingHpa, err := deploy.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(depl.ObjectMeta.Namespace).Get(hpaName, metav1.GetOptions{})
if err == nil {
return existingHpa, err
}
@@ -364,7 +396,7 @@ func (deploy *NewDeploy) createOrGetHpa(hpaName string, execStrategy *fission.Ex
hpa := asv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: hpaName,
Namespace: deploy.namespace,
Namespace: depl.ObjectMeta.Namespace,
Labels: depl.Labels,
},
Spec: asv1.HorizontalPodAutoscalerSpec{
@@ -379,7 +411,7 @@ func (deploy *NewDeploy) createOrGetHpa(hpaName string, execStrategy *fission.Ex
},
}
cHpa, err := deploy.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(deploy.namespace).Create(&hpa)
cHpa, err := deploy.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(depl.ObjectMeta.Namespace).Create(&hpa)
if err != nil {
return nil, err
}
@@ -392,11 +424,11 @@ func (deploy *NewDeploy) createOrGetHpa(hpaName string, execStrategy *fission.Ex
func (deploy *NewDeploy) getHpa(fn *crd.Function) (*asv1.HorizontalPodAutoscaler, error) {
hpaName := deploy.getObjName(fn)
return deploy.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(deploy.namespace).Get(hpaName, metav1.GetOptions{})
return deploy.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(fn.Metadata.Namespace).Get(hpaName, metav1.GetOptions{})
}
func (deploy *NewDeploy) updateHpa(hpa *asv1.HorizontalPodAutoscaler) error {
_, err := deploy.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(deploy.namespace).Update(hpa)
_, err := deploy.kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(hpa.ObjectMeta.Namespace).Update(hpa)
return err
}
@@ -405,9 +437,9 @@ func (deploy *NewDeploy) deleteHpa(ns string, name string) error {
return err
}
func (deploy *NewDeploy) createOrGetSvc(deployLabels map[string]string, svcName string) (*apiv1.Service, error) {
func (deploy *NewDeploy) createOrGetSvc(deployLabels map[string]string, svcName string, svcNamespace string) (*apiv1.Service, error) {
existingSvc, err := deploy.kubernetesClient.CoreV1().Services(deploy.namespace).Get(svcName, metav1.GetOptions{})
existingSvc, err := deploy.kubernetesClient.CoreV1().Services(svcNamespace).Get(svcName, metav1.GetOptions{})
if err == nil {
return existingSvc, err
}
@@ -436,7 +468,7 @@ func (deploy *NewDeploy) createOrGetSvc(deployLabels map[string]string, svcName
},
}
svc, err := deploy.kubernetesClient.CoreV1().Services(deploy.namespace).Create(service)
svc, err := deploy.kubernetesClient.CoreV1().Services(svcNamespace).Create(service)
if err != nil {
return nil, err
}
@@ -457,7 +489,7 @@ func (deploy *NewDeploy) deleteSvc(ns string, name string) error {
func (deploy *NewDeploy) waitForDeploy(depl *v1beta1.Deployment, replicas int32) (*v1beta1.Deployment, error) {
for i := 0; i < 120; i++ {
latestDepl, err := deploy.kubernetesClient.ExtensionsV1beta1().Deployments(deploy.namespace).Get(depl.Name, metav1.GetOptions{})
latestDepl, err := deploy.kubernetesClient.ExtensionsV1beta1().Deployments(depl.ObjectMeta.Namespace).Get(depl.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
+30 -7
View File
@@ -145,7 +145,7 @@ func (deploy *NewDeploy) Run(ctx context.Context) {
func (deploy *NewDeploy) initFuncController() (k8sCache.Store, k8sCache.Controller) {
resyncPeriod := 30 * time.Second
listWatch := k8sCache.NewListWatchFromClient(deploy.crdClient, "functions", metav1.NamespaceDefault, fields.Everything())
listWatch := k8sCache.NewListWatchFromClient(deploy.crdClient, "functions", metav1.NamespaceAll, fields.Everything())
store, controller := k8sCache.NewInformer(listWatch, &crd.Function{}, resyncPeriod, k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
fn := obj.(*crd.Function)
@@ -287,19 +287,26 @@ func (deploy *NewDeploy) fnCreate(fn *crd.Function) (*fscache.FuncSvc, error) {
deployLabels := deploy.getDeployLabels(fn, env)
// to support backward compatibility, if the function was created in default ns, we fall back to creating the
// deployment of the function in fission-function ns
ns := deploy.namespace
if fn.Metadata.Namespace != metav1.NamespaceDefault {
ns = fn.Metadata.Namespace
}
// Envoy(istio-proxy) returns 404 directly before istio pilot
// propagates latest Envoy-specific configuration.
// Since newdeploy waits for pods of deployment to be ready,
// change the order of kubeObject creation (create service first,
// then deployment) to take advantage of waiting time.
svc, err := deploy.createOrGetSvc(deployLabels, objName)
svc, err := deploy.createOrGetSvc(deployLabels, objName, ns)
if err != nil {
log.Printf("Error creating the service %v: %v", objName, err)
return fsvc, err
}
svcAddress := fmt.Sprintf("%v.%v", svc.Name, svc.Namespace)
depl, err := deploy.createOrGetDeployment(fn, env, objName, deployLabels)
depl, err := deploy.createOrGetDeployment(fn, env, objName, deployLabels, ns)
if err != nil {
log.Printf("Error creating the deployment %v: %v", objName, err)
return fsvc, err
@@ -468,7 +475,15 @@ func (deploy *NewDeploy) fnUpdate(oldFn *crd.Function, newFn *crd.Function) {
updateStatus(oldFn, err, "failed to get new deployment spec while updating function")
return
}
err = deploy.updateDeployment(newDeployment)
// to support backward compatibility, if the function was created in default ns, we fall back to creating the
// deployment of the function in fission-function ns
ns := deploy.namespace
if newFn.Metadata.Namespace != metav1.NamespaceDefault {
ns = newFn.Metadata.Namespace
}
err = deploy.updateDeployment(newDeployment, ns)
if err != nil {
updateStatus(oldFn, err, "failed to update deployment while updating function")
return
@@ -499,19 +514,26 @@ func (deploy *NewDeploy) fnDelete(fn *crd.Function) (*fscache.FuncSvc, error) {
}
objName := fsvc.Name
err = deploy.deleteDeployment(deploy.namespace, objName)
// to support backward compatibility, if the function was created in default ns, we fall back to creating the
// deployment of the function in fission-function ns, so cleaning up resources there
ns := deploy.namespace
if fn.Metadata.Namespace != metav1.NamespaceDefault {
ns = fn.Metadata.Namespace
}
err = deploy.deleteDeployment(ns, objName)
if err != nil {
log.Printf("Error deleting the deployment: %v", objName)
delError = err
}
err = deploy.deleteSvc(deploy.namespace, objName)
err = deploy.deleteSvc(ns, objName)
if err != nil {
log.Printf("Error deleting the service: %v", objName)
delError = err
}
err = deploy.deleteHpa(deploy.namespace, objName)
err = deploy.deleteHpa(ns, objName)
if err != nil {
log.Printf("Error deleting the HPA: %v", objName)
delError = err
@@ -520,6 +542,7 @@ func (deploy *NewDeploy) fnDelete(fn *crd.Function) (*fscache.FuncSvc, error) {
if delError != nil {
return nil, delError
}
return nil, nil
}
+125 -56
View File
@@ -26,7 +26,6 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes"
apiv1 "k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/rest"
k8sCache "k8s.io/client-go/tools/cache"
"github.com/fission/fission"
@@ -39,15 +38,15 @@ func getIstioServiceLabels(fnName string) map[string]string {
}
}
func makeFuncIstioServiceRegister(crdClient *rest.RESTClient,
kubernetesClient *kubernetes.Clientset, fnNamespace string) k8sCache.Controller {
func (gpm *GenericPoolManager) makeFuncController(fissionClient *crd.FissionClient,
kubernetesClient *kubernetes.Clientset, fissionfnNamespace string, istioEnabled bool) (k8sCache.Store, k8sCache.Controller) {
resyncPeriod := 30 * time.Second
lw := k8sCache.NewListWatchFromClient(crdClient, "functions", metav1.NamespaceDefault, fields.Everything())
_, controller := k8sCache.NewInformer(lw, &crd.Function{}, resyncPeriod,
lw := k8sCache.NewListWatchFromClient(fissionClient.GetCrdClient(), "functions", metav1.NamespaceAll, fields.Everything())
funcStore, controller := k8sCache.NewInformer(lw, &crd.Function{}, resyncPeriod,
k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
fn := obj.(*crd.Function)
// Since istio only allows accessing pod through k8s service,
@@ -56,70 +55,140 @@ func makeFuncIstioServiceRegister(crdClient *rest.RESTClient,
// Functions with executor type "Newdeploy" is specialized at
// pod starts. In this case, just ignore such functions.
fnExecutorType := fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType
if fnExecutorType != fission.ExecutorTypePoolmgr {
// In some cases, user may not enter the executorType explicitly, for example in his spec.yaml.
// we assume it to be of type poolmgr
if fnExecutorType != "" && fnExecutorType != fission.ExecutorTypePoolmgr {
return
}
// create a same name service for function
// since istio only allows the traffic to service
sel := map[string]string{
"functionName": fn.Metadata.Name,
"functionUid": string(fn.Metadata.UID),
// create or update role-binding
envNs := fissionfnNamespace
if fn.Spec.Environment.Namespace != metav1.NamespaceDefault {
envNs = fn.Spec.Environment.Namespace
}
svcName := fission.GetFunctionIstioServiceName(fn.Metadata.Name, fn.Metadata.Namespace)
// TODO : Just bring to your attention during review :
// setup rolebinding is tried, if it fails, we dont return. we just log an error and move on, because :
// 1. not all functions have secrets and/or configmaps, so things will work without this rolebinding in that case.
// 2. on the contrary, when the route is tried, the env fetcher logs will show a 403 forbidden message and same will be relayed to executor.
err := fission.SetupRoleBinding(kubernetesClient, fission.SecretConfigMapGetterRB, fn.Metadata.Namespace, fission.SecretConfigMapGetterCR, fission.ClusterRole, fission.FissionFetcherSA, envNs)
if err != nil {
log.Printf("Error : %v creating %s RoleBinding", err, fission.SecretConfigMapGetterRB)
} else {
log.Printf("Successfully set up rolebinding for fetcher SA: %s.%s, in func's ns for func : %s", fission.FissionFetcherSA, envNs, fn.Metadata.Name, fn.Metadata.Namespace)
}
// service for accepting user traffic
svc := apiv1.Service{
ObjectMeta: metav1.ObjectMeta{
Namespace: fnNamespace,
Name: svcName,
Labels: getIstioServiceLabels(fn.Metadata.Name),
},
Spec: apiv1.ServiceSpec{
Type: apiv1.ServiceTypeClusterIP,
Ports: []apiv1.ServicePort{
// Service port name should begin with a recognized prefix, or the traffic will be
// treated as TCP traffic. (https://istio.io/docs/setup/kubernetes/sidecar-injection.html)
// Originally the ports' name are similar to "http-fetch" and "http-specialize".
// But for istio 0.5.1, istio-proxy return unexpected 431 error with such naming.
// https://github.com/istio/istio/issues/928
// Workaround: remove prefix
// TODO: prepend prefix once the bug fixed
{
Name: "fetch",
Protocol: apiv1.ProtocolTCP,
Port: 8000,
TargetPort: intstr.FromInt(8000),
},
{
Name: "specialize",
Protocol: apiv1.ProtocolTCP,
Port: 8888,
TargetPort: intstr.FromInt(8888),
},
if istioEnabled {
// create a same name service for function
// since istio only allows the traffic to service
sel := map[string]string{
"functionName": fn.Metadata.Name,
"functionUid": string(fn.Metadata.UID),
}
svcName := fission.GetFunctionIstioServiceName(fn.Metadata.Name, fn.Metadata.Namespace)
// service for accepting user traffic
svc := apiv1.Service{
ObjectMeta: metav1.ObjectMeta{
Namespace: envNs,
Name: svcName,
Labels: getIstioServiceLabels(fn.Metadata.Name),
},
Selector: sel,
},
}
Spec: apiv1.ServiceSpec{
Type: apiv1.ServiceTypeClusterIP,
Ports: []apiv1.ServicePort{
// Service port name should begin with a recognized prefix, or the traffic will be
// treated as TCP traffic. (https://istio.io/docs/setup/kubernetes/sidecar-injection.html)
// Originally the ports' name are similar to "http-fetch" and "http-specialize".
// But for istio 0.5.1, istio-proxy return unexpected 431 error with such naming.
// https://github.com/istio/istio/issues/928
// Workaround: remove prefix
// TODO: prepend prefix once the bug fixed
{
Name: "fetch",
Protocol: apiv1.ProtocolTCP,
Port: 8000,
TargetPort: intstr.FromInt(8000),
},
{
Name: "specialize",
Protocol: apiv1.ProtocolTCP,
Port: 8888,
TargetPort: intstr.FromInt(8888),
},
},
Selector: sel,
},
}
// create function istio service if it does not exist
_, err := kubernetesClient.CoreV1().Services(fnNamespace).Create(&svc)
if err != nil && !kerrors.IsAlreadyExists(err) {
log.Printf("Error creating function istio service: %v", err)
// create function istio service if it does not exist
_, err = kubernetesClient.CoreV1().Services(envNs).Create(&svc)
if err != nil && !kerrors.IsAlreadyExists(err) {
log.Printf("Error creating function istio service: %v", err)
}
}
},
DeleteFunc: func(obj interface{}) {
fn := obj.(*crd.Function)
svcName := fission.GetFunctionIstioServiceName(fn.Metadata.Name, fn.Metadata.Namespace)
// delete function istio service
err := kubernetesClient.CoreV1().Services(fnNamespace).Delete(svcName, nil)
if err != nil && !kerrors.IsNotFound(err) {
log.Printf("Error deleting function istio service: %v", err)
fnExecutorType := fn.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType
if fnExecutorType != "" && fnExecutorType != fission.ExecutorTypePoolmgr {
return
}
envNs := fissionfnNamespace
if fn.Spec.Environment.Namespace != metav1.NamespaceDefault {
envNs = fn.Spec.Environment.Namespace
}
if istioEnabled {
svcName := fission.GetFunctionIstioServiceName(fn.Metadata.Name, fn.Metadata.Namespace)
// delete function istio service
err := kubernetesClient.CoreV1().Services(envNs).Delete(svcName, nil)
if err != nil && !kerrors.IsNotFound(err) {
log.Printf("Error deleting function istio service: %v", err)
}
}
},
UpdateFunc: func(oldObj, newObj interface{}) {
oldFunc := oldObj.(*crd.Function)
newFunc := newObj.(*crd.Function)
if oldFunc.Metadata.ResourceVersion == newFunc.Metadata.ResourceVersion {
return
}
envChanged := (oldFunc.Spec.Environment.Namespace != newFunc.Spec.Environment.Namespace)
executorTypeChangedToPM := (oldFunc.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType != fission.ExecutorTypePoolmgr &&
newFunc.Spec.InvokeStrategy.ExecutionStrategy.ExecutorType == fission.ExecutorTypePoolmgr)
// if a func's env reference gets updated and the newly referenced env is in a different ns,
// we need to create a rolebinding in func's ns so that the fetcher-sa in env ns has access
// to fetch secrets and config maps from the func's ns.
// similarly if executorType changed to Pool Manager, we now need a rolebinding in the func ns for fetcher sa
// present in env ns because for newdeploy, the fetcher sa is in function namespace
if envChanged || executorTypeChangedToPM {
envNs := fissionfnNamespace
if newFunc.Spec.Environment.Namespace != metav1.NamespaceDefault {
envNs = newFunc.Spec.Environment.Namespace
}
err := fission.SetupRoleBinding(kubernetesClient, fission.SecretConfigMapGetterRB,
newFunc.Metadata.Namespace, fission.SecretConfigMapGetterCR, fission.ClusterRole,
fission.FissionFetcherSA, envNs)
if err != nil {
log.Printf("Error : %v creating GetSecretConfigMapRoleBinding", err)
} else {
log.Printf("Set up rolebinding for fetcher SA in func's env ns : %s, in func's ns : %s, for func : %s", newFunc.Spec.Environment.Namespace, newFunc.Metadata.Namespace, newFunc.Metadata.Name)
}
}
},
UpdateFunc: func(oldObj, newObj interface{}) {},
})
return controller
return funcStore, controller
}
+28 -18
View File
@@ -56,6 +56,7 @@ type (
replicas int32 // num idle pods
deployment *v1beta1.Deployment // kubernetes deployment
namespace string // namespace to keep our resources
functionNamespace string // fallback namespace for fission functions
podReadyTimeout time.Duration // timeout for generic pods to become ready
idlePodReapTime time.Duration // pods unused for idlePodReapTime are deleted
fsCache *fscache.FunctionServiceCache // cache funcSvc's by function, address and podname
@@ -103,6 +104,7 @@ func MakeGenericPool(
env *crd.Environment,
initialReplicas int32,
namespace string,
functionNamespace string,
fsCache *fscache.FunctionServiceCache,
instanceId string,
enableIstio bool) (*GenericPool, error) {
@@ -125,23 +127,24 @@ func MakeGenericPool(
// TODO: in general we need to provide the user a way to configure pools. Initial
// replicas, autoscaling params, various timeouts, etc.
gp := &GenericPool{
env: env,
replicas: initialReplicas, // TODO make this an env param instead?
requestChannel: make(chan *choosePodRequest),
fissionClient: fissionClient,
kubernetesClient: kubernetesClient,
namespace: namespace,
podReadyTimeout: 5 * time.Minute, // TODO make this an env param?
idlePodReapTime: 3 * time.Minute, // TODO make this configurable
fsCache: fsCache,
poolInstanceId: uniuri.NewLen(8),
instanceId: instanceId,
fetcherImage: fetcherImage,
useSvc: false, // defaults off -- svc takes a second or more to become routable, slowing cold start
useIstio: enableIstio, // defaults off -- istio integration requires pod relabeling and it takes a second or more to become routable, slowing cold start
sharedMountPath: "/userfunc", // change this may break v1 compatibility, since most of the v1 environments have hard-coded "/userfunc" in loading path
sharedSecretPath: "/secrets",
sharedCfgMapPath: "/configs",
env: env,
replicas: initialReplicas, // TODO make this an env param instead?
requestChannel: make(chan *choosePodRequest),
fissionClient: fissionClient,
kubernetesClient: kubernetesClient,
namespace: namespace,
functionNamespace: functionNamespace,
podReadyTimeout: 5 * time.Minute, // TODO make this an env param?
idlePodReapTime: 3 * time.Minute, // TODO make this configurable
fsCache: fsCache,
poolInstanceId: uniuri.NewLen(8),
instanceId: instanceId,
fetcherImage: fetcherImage,
useSvc: false, // defaults off -- svc takes a second or more to become routable, slowing cold start
useIstio: enableIstio, // defaults off -- istio integration requires pod relabeling and it takes a second or more to become routable, slowing cold start
sharedMountPath: "/userfunc", // change this may break v1 compatibility, since most of the v1 environments have hard-coded "/userfunc" in loading path
sharedSecretPath: "/secrets",
sharedCfgMapPath: "/configs",
}
gp.runtimeImagePullPolicy = getImagePullPolicy(runtimeImagePullPolicy)
@@ -149,6 +152,13 @@ func MakeGenericPool(
gp.fetcherImagePullPolicy = getImagePullPolicy(fetcherImagePullPolicy)
log.Printf("fetcher image: %v, pull policy: %v", gp.fetcherImage, gp.fetcherImagePullPolicy)
// create fetcher SA in this ns, if not already created
_, err := fission.SetupSA(gp.kubernetesClient, fission.FissionFetcherSA, gp.namespace)
if err != nil {
log.Printf("Error : %v creating fetcher SA in ns : %s", err, gp.namespace)
return nil, err
}
// Labels for generic deployment/RS/pods.
gp.labelsForPool = map[string]string{
"environmentName": gp.env.Metadata.Name,
@@ -158,7 +168,7 @@ func MakeGenericPool(
}
// create the pool
err := gp.createPool()
err = gp.createPool()
if err != nil {
return nil, err
}
+22 -15
View File
@@ -52,8 +52,11 @@ type (
instanceId string
requestChannel chan *request
enableIstio bool
istioServiceRegister k8sCache.Controller
enableIstio bool
funcStore k8sCache.Store
funcController k8sCache.Controller
pkgStore k8sCache.Store
pkgController k8sCache.Controller
}
request struct {
requestType
@@ -92,20 +95,19 @@ func MakeGenericPoolManager(
log.Println("Failed to parse ENABLE_ISTIO")
}
gpm.enableIstio = istio
if gpm.enableIstio {
gpm.istioServiceRegister = makeFuncIstioServiceRegister(
gpm.fissionClient.GetCrdClient(), gpm.kubernetesClient, functionNamespace)
}
}
gpm.funcStore, gpm.funcController = gpm.makeFuncController(
gpm.fissionClient, gpm.kubernetesClient, gpm.namespace, gpm.enableIstio)
gpm.pkgStore, gpm.pkgController = gpm.makePkgController(gpm.fissionClient, gpm.kubernetesClient, gpm.namespace)
return gpm
}
func (gpm *GenericPoolManager) Run(ctx context.Context) {
if gpm.enableIstio && gpm.istioServiceRegister != nil {
go gpm.istioServiceRegister.Run(ctx.Done())
}
go gpm.funcController.Run(ctx.Done())
go gpm.pkgController.Run(ctx.Done())
}
func (gpm *GenericPoolManager) service() {
@@ -113,6 +115,7 @@ func (gpm *GenericPoolManager) service() {
req := <-gpm.requestChannel
switch req.requestType {
case GET_POOL:
// just because they are missing in the cache, we end up creating another duplicate pool.
var err error
pool, ok := gpm.pools[crd.CacheKey(&req.env.Metadata)]
if !ok {
@@ -122,9 +125,16 @@ func (gpm *GenericPoolManager) service() {
poolsize = 1
}
// To support backward compatibility, if envs are created in default ns, we go ahead
// and create pools in fission-function ns as earlier.
ns := gpm.namespace
if req.env.Metadata.Namespace != metav1.NamespaceDefault {
ns = req.env.Metadata.Namespace
}
pool, err = MakeGenericPool(
gpm.fissionClient, gpm.kubernetesClient, req.env, poolsize,
gpm.namespace, gpm.fsCache, gpm.instanceId, gpm.enableIstio)
ns, gpm.namespace, gpm.fsCache, gpm.instanceId, gpm.enableIstio)
if err != nil {
req.responseChannel <- &response{error: err}
continue
@@ -133,15 +143,12 @@ func (gpm *GenericPoolManager) service() {
}
req.responseChannel <- &response{pool: pool}
case CLEANUP_POOLS:
latestEnvSet := make(map[string]bool)
latestEnvPoolsize := make(map[string]int)
for _, env := range req.envList {
latestEnvSet[crd.CacheKey(&env.Metadata)] = true
latestEnvPoolsize[crd.CacheKey(&env.Metadata)] = int(gpm.getEnvPoolsize(&env))
}
for key, pool := range gpm.pools {
_, ok := latestEnvSet[key]
poolsize := latestEnvPoolsize[key]
poolsize, ok := latestEnvPoolsize[key]
if !ok || poolsize == 0 {
// Env no longer exists or pool size changed to zero
+91
View File
@@ -0,0 +1,91 @@
/*
Copyright 2018 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 poolmgr
import (
"time"
log "github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/client-go/kubernetes"
k8sCache "k8s.io/client-go/tools/cache"
"github.com/fission/fission"
"github.com/fission/fission/crd"
)
// TODO : It may make sense to make each of add, update, delete funcs run as separate go routines.
func (gpm *GenericPoolManager) makePkgController(fissionClient *crd.FissionClient,
kubernetesClient *kubernetes.Clientset, fissionfnNamespace string) (k8sCache.Store, k8sCache.Controller) {
resyncPeriod := 30 * time.Second
lw := k8sCache.NewListWatchFromClient(fissionClient.GetCrdClient(), "packages", metav1.NamespaceAll, fields.Everything())
pkgStore, controller := k8sCache.NewInformer(lw, &crd.Package{}, resyncPeriod,
k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
pkg := obj.(*crd.Package)
log.Printf("List watch for package reported a new package addition: %s.%s", pkg.Metadata.Name, pkg.Metadata.Namespace)
// create or update role-binding for fetcher sa in env ns to be able to get the pkg contents from pkg namespace
envNs := fissionfnNamespace
if pkg.Spec.Environment.Namespace != metav1.NamespaceDefault {
envNs = pkg.Spec.Environment.Namespace
}
// here, we return if we hit an error during rolebinding setup. this is because this rolebinding is mandatory for
// every function's package to be loaded into its env. without that, there's no point to move forward.
err := fission.SetupRoleBinding(kubernetesClient, fission.PackageGetterRB, pkg.Metadata.Namespace, fission.PackageGetterCR, fission.ClusterRole, fission.FissionFetcherSA, envNs)
if err != nil {
log.Printf("Error creating %s for package: %s.%s, err: %v", fission.PackageGetterRB, pkg.Metadata.Name, pkg.Metadata.Namespace, err)
return
}
log.Printf("Successfully set up rolebinding for fetcher SA: %s.%s, in packages's ns : %s, for pkg : %s", fission.FissionFetcherSA, envNs, pkg.Metadata.Namespace, pkg.Metadata.Name)
},
UpdateFunc: func(oldObj, newObj interface{}) {
oldPkg := oldObj.(*crd.Package)
newPkg := newObj.(*crd.Package)
if oldPkg.Metadata.ResourceVersion == newPkg.Metadata.ResourceVersion {
return
}
// if a pkg's env reference gets updated and the newly referenced env is in a different ns,
// we need to update the role-binding in pkg ns to grant permissions to the fetcher-sa in env ns
// to do a get on pkg
if oldPkg.Spec.Environment.Namespace != newPkg.Spec.Environment.Namespace {
envNs := fissionfnNamespace
if newPkg.Spec.Environment.Namespace != metav1.NamespaceDefault {
envNs = newPkg.Spec.Environment.Namespace
}
err := fission.SetupRoleBinding(kubernetesClient, fission.PackageGetterRB,
newPkg.Metadata.Namespace, fission.PackageGetterCR, fission.ClusterRole,
fission.FissionFetcherSA, envNs)
if err != nil {
log.Printf("Error : %v updating %s for package: %s.%s", err, fission.PackageGetterRB, newPkg.Metadata.Name, newPkg.Metadata.Namespace)
return
}
log.Printf("Updated rolebinding for fetcher SA: %s.%s, in packages's ns : %s, for pkg : %s", fission.FissionFetcherSA, envNs, newPkg.Metadata.Namespace, newPkg.Metadata.Name)
}
},
})
return pkgStore, controller
}
+3 -3
View File
@@ -29,8 +29,8 @@ func runRouter(port int, executorUrl string) {
log.Fatalf("Error: Router exited.")
}
func runExecutor(port int, fissionNamespace, functionNamespace string) {
err := executor.StartExecutor(fissionNamespace, functionNamespace, port)
func runExecutor(port int, fissionNamespace, functionNamespace, envBuilderNamespace string) {
err := executor.StartExecutor(fissionNamespace, functionNamespace, envBuilderNamespace, port)
if err != nil {
log.Fatalf("Error starting executor: %v", err)
}
@@ -166,7 +166,7 @@ Options:
if arguments["--executorPort"] != nil {
port := getPort(arguments["--executorPort"])
runExecutor(port, fissionNs, functionNs)
runExecutor(port, fissionNs, functionNs, envBuilderNs)
}
if arguments["--kubewatcher"] == true {
+1 -1
View File
@@ -66,7 +66,7 @@ func (w *packageBuildWatcher) watch(ctx context.Context) {
}
// poll list of packages (TODO: convert to watch)
pkgs, err := w.fclient.PackageList()
pkgs, err := w.fclient.PackageList(metav1.NamespaceAll)
checkErr(err, "Getting list of packages")
// find packages that (a) are in the app spec and (b) have an interesting
+3 -3
View File
@@ -205,10 +205,10 @@ func createArchive(client *client.Client, fileName string, specFile string) *fis
return &archive
}
func createPackage(client *client.Client, envName, srcArchiveName, deployArchiveName, buildcmd string, specFile string) *metav1.ObjectMeta {
func createPackage(client *client.Client, pkgNamespace, envName, envNamespace, srcArchiveName, deployArchiveName, buildcmd string, specFile string) *metav1.ObjectMeta {
pkgSpec := fission.PackageSpec{
Environment: fission.EnvironmentReference{
Namespace: metav1.NamespaceDefault,
Namespace: envNamespace,
Name: envName,
},
}
@@ -239,7 +239,7 @@ func createPackage(client *client.Client, envName, srcArchiveName, deployArchive
pkg := &crd.Package{
Metadata: metav1.ObjectMeta{
Name: pkgName,
Namespace: metav1.NamespaceDefault,
Namespace: pkgNamespace,
},
Spec: pkgSpec,
Status: fission.PackageStatus{
+33 -6
View File
@@ -28,9 +28,24 @@ import (
"k8s.io/client-go/pkg/api/v1"
"github.com/fission/fission"
"github.com/fission/fission/controller/client"
"github.com/fission/fission/crd"
)
func getFunctionsByEnvironment(client *client.Client, envName, envNamespace string) ([]crd.Function, error) {
fnList, err := client.FunctionList(metav1.NamespaceAll)
if err != nil {
return nil, err
}
fns := []crd.Function{}
for _, fn := range fnList {
if fn.Spec.Environment.Name == envName && fn.Spec.Environment.Namespace == envNamespace {
fns = append(fns, fn)
}
}
return fns, nil
}
func envCreate(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
@@ -38,6 +53,14 @@ func envCreate(c *cli.Context) error {
if len(envName) == 0 {
fatal("Need a name, use --name.")
}
envNamespace := c.String("envNamespace")
envList, err := client.EnvironmentList(envNamespace)
if err == nil && len(envList) > 0 {
warn(fmt.Sprintf("%d environment(s) are present in this ns: %s. All these envs share"+
" the same service account token, with previleges to view secrets of all the functions referencing them. "+
"Envs can be created in different ns if isolation is needed", len(envList), envNamespace))
}
var poolsize int
if c.IsSet("poolsize") {
@@ -80,7 +103,7 @@ func envCreate(c *cli.Context) error {
env := &crd.Environment{
Metadata: metav1.ObjectMeta{
Name: envName,
Namespace: metav1.NamespaceDefault,
Namespace: envNamespace,
},
Spec: fission.EnvironmentSpec{
Version: envVersion,
@@ -106,7 +129,7 @@ func envCreate(c *cli.Context) error {
return nil
}
_, err := client.EnvironmentCreate(env)
_, err = client.EnvironmentCreate(env)
checkErr(err, "create environment")
fmt.Printf("environment '%v' created\n", envName)
@@ -120,10 +143,11 @@ func envGet(c *cli.Context) error {
if len(envName) == 0 {
fatal("Need a name, use --name.")
}
envNamespace := c.String("envNamespace")
m := &metav1.ObjectMeta{
Name: envName,
Namespace: metav1.NamespaceDefault,
Namespace: envNamespace,
}
env, err := client.EnvironmentGet(m)
checkErr(err, "get environment")
@@ -143,6 +167,7 @@ func envUpdate(c *cli.Context) error {
if len(envName) == 0 {
fatal("Need a name, use --name.")
}
envNamespace := c.String("envNamespace")
envImg := c.String("image")
envBuilderImg := c.String("builder")
@@ -155,7 +180,7 @@ func envUpdate(c *cli.Context) error {
env, err := client.EnvironmentGet(&metav1.ObjectMeta{
Name: envName,
Namespace: metav1.NamespaceDefault,
Namespace: envNamespace,
})
checkErr(err, "find environment")
@@ -198,10 +223,11 @@ func envDelete(c *cli.Context) error {
if len(envName) == 0 {
fatal("Need a name , use --name.")
}
envNamespace := c.String("envNamespace")
m := &metav1.ObjectMeta{
Name: envName,
Namespace: metav1.NamespaceDefault,
Namespace: envNamespace,
}
err := client.EnvironmentDelete(m)
checkErr(err, "delete environment")
@@ -212,8 +238,9 @@ func envDelete(c *cli.Context) error {
func envList(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
envNamespace := c.String("envNamespace")
envs, err := client.EnvironmentList()
envs, err := client.EnvironmentList(envNamespace)
checkErr(err, "list environments")
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
+82 -38
View File
@@ -30,6 +30,7 @@ import (
"github.com/satori/go.uuid"
"github.com/urfave/cli"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apiv1 "k8s.io/client-go/pkg/api/v1"
@@ -115,9 +116,13 @@ func getTargetCPU(c *cli.Context) int {
return targetCPU
}
// From this change onwards, we mandate that a function should reference a secret, config map and package in its own ns
func fnCreate(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
fnNamespace := c.String("fnNamespace")
envNamespace := c.String("envNamespace")
fnName := c.String("name")
if len(fnName) == 0 {
fatal("Need --name argument.")
@@ -131,7 +136,8 @@ func fnCreate(c *cli.Context) error {
specFile = fmt.Sprintf("function-%v.yaml", fnName)
}
fnList, err := client.FunctionList()
// check for unique function names within a namespace
fnList, err := client.FunctionList(fnNamespace)
checkErr(err, "get function list")
// check function existence before creating package
for _, fn := range fnList {
@@ -148,18 +154,16 @@ func fnCreate(c *cli.Context) error {
secretName := c.String("secret")
cfgMapName := c.String("configmap")
secretNameSpace := c.String("secretNamespace")
cfgMapNameSpace := c.String("configmapNamespace")
if len(pkgName) > 0 {
// use existing package
pkg, err := client.PackageGet(&metav1.ObjectMeta{
Namespace: metav1.NamespaceDefault,
Namespace: fnNamespace,
Name: pkgName,
})
checkErr(err, fmt.Sprintf("read package '%v'", pkgName))
checkErr(err, fmt.Sprintf("read package in '%v' in Namespace: %s. Package needs to be present in the same namespace as function", pkgName, fnNamespace))
pkgMetadata = &pkg.Metadata
envName = pkg.Spec.Environment.Name
envNamespace = pkg.Spec.Environment.Namespace
} else {
// need to specify environment for creating new package
envName = c.String("env")
@@ -169,7 +173,7 @@ func fnCreate(c *cli.Context) error {
// examine existence of given environment
_, err := client.EnvironmentGet(&metav1.ObjectMeta{
Namespace: metav1.NamespaceDefault,
Namespace: envNamespace,
Name: envName,
})
if err != nil {
@@ -192,8 +196,8 @@ func fnCreate(c *cli.Context) error {
buildcmd := c.String("buildcmd")
// create new package
pkgMetadata = createPackage(client, envName, srcArchiveName, deployArchiveName, buildcmd, specFile)
// create new package in the same namespace as the function.
pkgMetadata = createPackage(client, fnNamespace, envName, envNamespace, srcArchiveName, deployArchiveName, buildcmd, specFile)
}
invokeStrategy := getInvokeStrategy(c.Int("minscale"), c.Int("maxscale"), c.String("executortype"), getTargetCPU(c))
@@ -207,23 +211,35 @@ func fnCreate(c *cli.Context) error {
var cfgmaps []fission.ConfigMapReference
if len(secretName) > 0 {
if len(secretNameSpace) == 0 {
secretNameSpace = metav1.NamespaceDefault
// check the referenced secret is in the same ns as the function, if not give a warning.
_, err := client.SecretGet(&metav1.ObjectMeta{
Namespace: fnNamespace,
Name: secretName,
})
if k8serrors.IsNotFound(err) {
warn(fmt.Sprintf("Secret %s not found in Namespace: %s. Secret needs to be present in the same namespace as function", secretName, fnNamespace))
}
newSecret := fission.SecretReference{
Name: secretName,
Namespace: secretNameSpace,
Namespace: fnNamespace,
}
secrets = []fission.SecretReference{newSecret}
}
if len(cfgMapName) > 0 {
if len(cfgMapNameSpace) == 0 {
cfgMapNameSpace = metav1.NamespaceDefault
// check the referenced cfgmap is in the same ns as the function, if not give a warning.
_, err := client.ConfigMapGet(&metav1.ObjectMeta{
Namespace: fnNamespace,
Name: cfgMapName,
})
if k8serrors.IsNotFound(err) {
warn(fmt.Sprintf("ConfigMap %s not found in Namespace: %s. ConfigMap needs to be present in the same namespace as function", cfgMapName, fnNamespace))
}
newCfgMap := fission.ConfigMapReference{
Name: cfgMapName,
Namespace: cfgMapNameSpace,
Namespace: fnNamespace,
}
cfgmaps = []fission.ConfigMapReference{newCfgMap}
}
@@ -231,12 +247,12 @@ func fnCreate(c *cli.Context) error {
function := &crd.Function{
Metadata: metav1.ObjectMeta{
Name: fnName,
Namespace: metav1.NamespaceDefault,
Namespace: fnNamespace,
},
Spec: fission.FunctionSpec{
Environment: fission.EnvironmentReference{
Name: envName,
Namespace: metav1.NamespaceDefault,
Namespace: envNamespace,
},
Package: fission.FunctionPackageRef{
FunctionName: entrypoint,
@@ -283,7 +299,7 @@ func fnCreate(c *cli.Context) error {
ht := &crd.HTTPTrigger{
Metadata: metav1.ObjectMeta{
Name: triggerName,
Namespace: metav1.NamespaceDefault,
Namespace: fnNamespace,
},
Spec: fission.HTTPTriggerSpec{
RelativeURL: triggerUrl,
@@ -308,9 +324,10 @@ func fnGet(c *cli.Context) error {
if len(fnName) == 0 {
fatal("Need name of function, use --name")
}
fnNamespace := c.String("fnNamespace")
m := &metav1.ObjectMeta{
Name: fnName,
Namespace: metav1.NamespaceDefault,
Namespace: fnNamespace,
}
fn, err := client.FunctionGet(m)
checkErr(err, "get function")
@@ -332,10 +349,11 @@ func fnGetMeta(c *cli.Context) error {
if len(fnName) == 0 {
fatal("Need name of function, use --name")
}
fnNamespace := c.String("fnNamespace")
m := &metav1.ObjectMeta{
Name: fnName,
Namespace: metav1.NamespaceDefault,
Namespace: fnNamespace,
}
f, err := client.FunctionGet(m)
@@ -364,14 +382,16 @@ func fnUpdate(c *cli.Context) error {
if len(fnName) == 0 {
fatal("Need name of function, use --name")
}
fnNamespace := c.String("fnNamespace")
function, err := client.FunctionGet(&metav1.ObjectMeta{
Name: fnName,
Namespace: metav1.NamespaceDefault,
Namespace: fnNamespace,
})
checkErr(err, fmt.Sprintf("read function '%v'", fnName))
envName := c.String("env")
envNamespace := c.String("envNamespace")
deployArchiveName := c.String("code")
if len(deployArchiveName) == 0 {
deployArchiveName = c.String("deploy")
@@ -385,9 +405,6 @@ func fnUpdate(c *cli.Context) error {
secretName := c.String("secret")
cfgMapName := c.String("configmap")
secretNameSpace := c.String("secretNamespace")
cfgMapNameSpace := c.String("configmapNamespace")
if len(srcArchiveName) > 0 && len(deployArchiveName) > 0 {
fatal("Need either of --src or --deploy and not both arguments.")
}
@@ -396,12 +413,19 @@ func fnUpdate(c *cli.Context) error {
if len(function.Spec.Secrets) > 1 {
fatal("Please use 'fission spec apply' to update list of secrets")
}
if len(secretNameSpace) == 0 {
secretNameSpace = metav1.NamespaceDefault
// check that the referenced secret is in the same ns as the function, if not give a warning.
_, err := client.SecretGet(&metav1.ObjectMeta{
Namespace: fnNamespace,
Name: secretName,
})
if k8serrors.IsNotFound(err) {
warn(fmt.Sprintf("secret %s not found in Namespace: %s. Secret needs to be present in the same namespace as function", secretName, fnNamespace))
}
newSecret := fission.SecretReference{
Name: secretName,
Namespace: secretNameSpace,
Namespace: fnNamespace,
}
function.Spec.Secrets = []fission.SecretReference{newSecret}
}
@@ -410,18 +434,26 @@ func fnUpdate(c *cli.Context) error {
if len(function.Spec.ConfigMaps) > 1 {
fatal("Please use 'fission spec apply' to update list of configmaps")
}
if len(cfgMapNameSpace) == 0 {
cfgMapNameSpace = metav1.NamespaceDefault
// check that the referenced cfgmap is in the same ns as the function, if not give a warning.
_, err := client.ConfigMapGet(&metav1.ObjectMeta{
Namespace: fnNamespace,
Name: cfgMapName,
})
if k8serrors.IsNotFound(err) {
warn(fmt.Sprintf("ConfigMap %s not found in Namespace: %s. ConfigMap needs to be present in the same namespace as the function", cfgMapName, fnNamespace))
}
newCfgMap := fission.ConfigMapReference{
Name: cfgMapName,
Namespace: cfgMapNameSpace,
Namespace: fnNamespace,
}
function.Spec.ConfigMaps = []fission.ConfigMapReference{newCfgMap}
}
if len(envName) > 0 {
function.Spec.Environment.Name = envName
function.Spec.Environment.Namespace = envNamespace
}
if len(entrypoint) > 0 {
@@ -432,22 +464,22 @@ func fnUpdate(c *cli.Context) error {
}
pkg, err := client.PackageGet(&metav1.ObjectMeta{
Namespace: metav1.NamespaceDefault,
Namespace: fnNamespace,
Name: pkgName,
})
checkErr(err, fmt.Sprintf("read package '%v'", pkgName))
checkErr(err, fmt.Sprintf("read package '%v.%v'. Pkg should be present in the same ns as the function", pkgName, fnNamespace))
pkgMetadata := &pkg.Metadata
if len(deployArchiveName) != 0 || len(srcArchiveName) != 0 || len(buildcmd) != 0 || len(envName) != 0 {
fnList, err := getFunctionsByPackage(client, pkg.Metadata.Name)
fnList, err := getFunctionsByPackage(client, pkg.Metadata.Name, pkg.Metadata.Namespace)
checkErr(err, "get function list")
if !force && len(fnList) > 1 {
fatal("Package is used by multiple functions, use --force to force update")
}
pkgMetadata = updatePackage(client, pkg, envName, srcArchiveName, deployArchiveName, buildcmd)
pkgMetadata = updatePackage(client, pkg, envName, envNamespace, srcArchiveName, deployArchiveName, buildcmd)
checkErr(err, fmt.Sprintf("update package '%v'", pkgName))
fmt.Printf("package '%v' updated\n", pkgMetadata.GetName())
@@ -463,6 +495,9 @@ func fnUpdate(c *cli.Context) error {
}
}
// TODO : One corner case where user just updates the pkg reference with fnUpdate, but internally this new pkg reference
// references a diff env than the spec
// update function spec with new package metadata
function.Spec.Package.PackageRef = fission.PackageRef{
Namespace: pkgMetadata.Namespace,
@@ -532,10 +567,11 @@ func fnDelete(c *cli.Context) error {
if len(fnName) == 0 {
fatal("Need name of function, use --name")
}
fnNamespace := c.String("fnNamespace")
m := &metav1.ObjectMeta{
Name: fnName,
Namespace: metav1.NamespaceDefault,
Namespace: fnNamespace,
}
err := client.FunctionDelete(m)
@@ -547,8 +583,9 @@ func fnDelete(c *cli.Context) error {
func fnList(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
ns := c.String("fnNamespace")
fns, err := client.FunctionList()
fns, err := client.FunctionList(ns)
checkErr(err, "list functions")
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
@@ -581,6 +618,7 @@ func fnLogs(c *cli.Context) error {
if len(fnName) == 0 {
fatal("Need name of function, use --name")
}
fnNamespace := c.String("fnNamespace")
dbType := c.String("dbtype")
if len(dbType) == 0 {
@@ -590,7 +628,7 @@ func fnLogs(c *cli.Context) error {
fnPod := c.String("pod")
m := &metav1.ObjectMeta{
Name: fnName,
Namespace: metav1.NamespaceDefault,
Namespace: fnNamespace,
}
recordLimit := c.Int("recordcount")
@@ -659,6 +697,7 @@ func fnTest(c *cli.Context) error {
if len(fnName) == 0 {
fatal("Need function name to be specified with --name")
}
ns := c.String("fnNamespace")
routerURL := os.Getenv("FISSION_ROUTER")
if len(routerURL) == 0 {
@@ -670,7 +709,12 @@ func fnTest(c *cli.Context) error {
routerURL = strings.TrimPrefix(routerURL, "http://")
}
url := fmt.Sprintf("http://%s/fission-function/%s", routerURL, fnName)
fnUri := fnName
if ns != metav1.NamespaceDefault {
fnUri = fmt.Sprintf("%v/%v", ns, fnName)
}
url := fmt.Sprintf("http://%s/fission-function/%s", routerURL, fnUri)
resp := httpRequest(c.String("method"), url, c.String("body"), c.StringSlice("header"))
if resp.StatusCode < 400 {
+13 -8
View File
@@ -58,10 +58,10 @@ func getMethod(method string) string {
return ""
}
func checkFunctionExistence(fissionClient *client.Client, fnName string) {
func checkFunctionExistence(fissionClient *client.Client, fnName string, fnNamespace string) {
meta := &metav1.ObjectMeta{
Name: fnName,
Namespace: metav1.NamespaceDefault,
Namespace: fnNamespace,
}
_, err := fissionClient.FunctionGet(meta)
@@ -77,6 +77,8 @@ func htCreate(c *cli.Context) error {
if len(fnName) == 0 {
fatal("Need a function name to create a trigger, use --function")
}
fnNamespace := c.String("fnNamespace")
triggerUrl := c.String("url")
if len(triggerUrl) == 0 {
fatal("Need a trigger URL, use --url")
@@ -90,7 +92,7 @@ func htCreate(c *cli.Context) error {
method = "GET"
}
checkFunctionExistence(client, fnName)
checkFunctionExistence(client, fnName, fnNamespace)
// just name triggers by uuid.
triggerName := uuid.NewV4().String()
@@ -98,7 +100,7 @@ func htCreate(c *cli.Context) error {
ht := &crd.HTTPTrigger{
Metadata: metav1.ObjectMeta{
Name: triggerName,
Namespace: metav1.NamespaceDefault,
Namespace: fnNamespace,
},
Spec: fission.HTTPTriggerSpec{
RelativeURL: triggerUrl,
@@ -135,6 +137,7 @@ func htUpdate(c *cli.Context) error {
if len(htName) == 0 {
fatal("Need name of trigger, use --name")
}
triggerNamespace := c.String("triggerNamespace")
// update function ref
newFn := c.String("function")
@@ -142,11 +145,11 @@ func htUpdate(c *cli.Context) error {
fatal("Nothing to update. Use --function to specify a new function.")
}
checkFunctionExistence(client, newFn)
checkFunctionExistence(client, newFn, triggerNamespace)
ht, err := client.HTTPTriggerGet(&metav1.ObjectMeta{
Name: htName,
Namespace: metav1.NamespaceDefault,
Namespace: triggerNamespace,
})
checkErr(err, "get HTTP trigger")
@@ -167,10 +170,11 @@ func htDelete(c *cli.Context) error {
if len(htName) == 0 {
fatal("Need name of trigger to delete, use --name")
}
triggerNamespace := c.String("triggerNamespace")
err := client.HTTPTriggerDelete(&metav1.ObjectMeta{
Name: htName,
Namespace: metav1.NamespaceDefault,
Namespace: triggerNamespace,
})
checkErr(err, "delete trigger")
@@ -180,8 +184,9 @@ func htDelete(c *cli.Context) error {
func htList(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
triggerNamespace := c.String("triggerNamespace")
hts, err := client.HTTPTriggerList()
hts, err := client.HTTPTriggerList(triggerNamespace)
checkErr(err, "list HTTP triggers")
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
+51 -44
View File
@@ -26,6 +26,7 @@ import (
version "github.com/fission/fission"
"github.com/urfave/cli"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func getFissionNamespace() string {
@@ -107,6 +108,12 @@ func main() {
// all resource create commands accept --spec
specSaveFlag := cli.BoolFlag{Name: "spec", Usage: "Save to the spec directory instead of creating on cluster"}
// namespace reference for all objects
fnNamespaceFlag := cli.StringFlag{Name: "fnNamespace, fns", Value: metav1.NamespaceDefault, Usage: "Namespace for function object"}
envNamespaceFlag := cli.StringFlag{Name: "envNamespace, envns", Value: metav1.NamespaceDefault, Usage: "Namespace for environment object"}
pkgNamespaceFlag := cli.StringFlag{Name: "pkgNamespace, pkgns", Value: metav1.NamespaceDefault, Usage: "Namespace for package object"}
triggerNamespaceFlag := cli.StringFlag{Name: "triggerNamespace, triggerns", Value: metav1.NamespaceDefault, Usage: "Namespace for trigger object"}
// trigger method and url flags (used in function and route CLIs)
htMethodFlag := cli.StringFlag{Name: "method", Value: "GET", Usage: "HTTP Method: GET|POST|PUT|DELETE|HEAD"}
htUrlFlag := cli.StringFlag{Name: "url", Usage: "URL pattern (See gorilla/mux supported patterns)"}
@@ -126,7 +133,7 @@ func main() {
fnCodeFlag := cli.StringFlag{Name: "code", Usage: "local path or URL for source code"}
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"}
fnPkgNameFlag := cli.StringFlag{Name: "pkgname, pkg", Usage: "Name of the existing package (--deploy and --src and --env will be ignored)"}
fnPkgNameFlag := cli.StringFlag{Name: "pkgname, pkg", Usage: "Name of the existing package (--deploy and --src and --env will be ignored), should be in the same namespace as the function"}
fnPodFlag := cli.StringFlag{Name: "pod", Usage: "function pod name, optional (use latest if unspecified)"}
fnFollowFlag := cli.BoolFlag{Name: "follow, f", Usage: "specify if the logs should be streamed"}
fnDetailFlag := cli.BoolFlag{Name: "detail, d", Usage: "display detailed information"}
@@ -135,34 +142,34 @@ func main() {
fnHeaderFlag := cli.StringSliceFlag{Name: "header, H", Usage: "request headers"}
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"}
fnSecretFlag := cli.StringFlag{Name: "secret", Usage: "function access to secret"}
fnSecretnsFlag := cli.StringFlag{Name: "secretNamespace", Usage: "namespace of secret"}
fnCfgMapFlag := cli.StringFlag{Name: "configmap", Usage: "function access to configmap"}
fnCfgMapnsFlag := cli.StringFlag{Name: "configmapNamespace", Usage: "namespace of configmap"}
fnSecretFlag := cli.StringFlag{Name: "secret", Usage: "function access to secret, should be present in the same namespace as the function"}
fnCfgMapFlag := cli.StringFlag{Name: "configmap", Usage: "function access to configmap, should be present in the same namespace as the function"}
fnLogCountFlag := cli.StringFlag{Name: "recordcount", Usage: "the n most recent log records"}
fnForceFlag := cli.BoolFlag{Name: "force", Usage: "Force update a package even if it is used by one or more functions"}
fnExecutorTypeFlag := cli.StringFlag{Name: "executortype", Value: "poolmgr", Usage: "Executor type for execution; one of 'poolmgr', 'newdeploy'"}
fnExecutorTypeFlag := cli.StringFlag{Name: "executortype", Usage: "Executor type for execution; one of 'poolmgr', 'newdeploy' defaults to 'poolmgr'"}
fnSubcommands := []cli.Command{
{Name: "create", Usage: "Create new function (and optionally, an HTTP route to it)", Flags: []cli.Flag{fnNameFlag, fnEnvNameFlag, specSaveFlag, fnCodeFlag, fnSrcArchiveFlag, fnDeployArchiveFlag, fnEntryPointFlag, fnBuildCmdFlag, fnPkgNameFlag, htUrlFlag, htMethodFlag, minCpu, maxCpu, minMem, maxMem, minScale, maxScale, fnExecutorTypeFlag, targetcpu, fnCfgMapFlag, fnSecretFlag, fnSecretnsFlag, fnCfgMapnsFlag}, Action: fnCreate},
{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: "update", Usage: "Update function source code", Flags: []cli.Flag{fnNameFlag, fnEnvNameFlag, fnCodeFlag, fnSrcArchiveFlag, fnDeployArchiveFlag, fnEntryPointFlag, fnPkgNameFlag, fnBuildCmdFlag, fnForceFlag, minCpu, maxCpu, minMem, maxMem, minScale, maxScale, fnExecutorTypeFlag, targetcpu}, Action: fnUpdate},
{Name: "delete", Usage: "Delete function", Flags: []cli.Flag{fnNameFlag}, Action: fnDelete},
{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, fnLogCountFlag}, Action: fnLogs},
{Name: "test", Usage: "Test a function", Flags: []cli.Flag{fnNameFlag, fnEnvNameFlag, fnCodeFlag, fnSrcArchiveFlag, htMethodFlag, fnBodyFlag, fnHeaderFlag}, Action: fnTest},
{Name: "create", Usage: "Create new function (and optionally, an HTTP route to it)", Flags: []cli.Flag{fnNameFlag, fnNamespaceFlag, fnEnvNameFlag, envNamespaceFlag, specSaveFlag, fnCodeFlag, fnSrcArchiveFlag, fnDeployArchiveFlag, fnEntryPointFlag, fnBuildCmdFlag, fnPkgNameFlag, htUrlFlag, htMethodFlag, minCpu, maxCpu, minMem, maxMem, minScale, maxScale, fnExecutorTypeFlag, targetcpu, fnCfgMapFlag, fnSecretFlag}, Action: fnCreate},
{Name: "get", Usage: "Get function source code", Flags: []cli.Flag{fnNameFlag, fnNamespaceFlag}, Action: fnGet},
{Name: "getmeta", Usage: "Get function metadata", Flags: []cli.Flag{fnNameFlag, fnNamespaceFlag}, Action: fnGetMeta},
{Name: "update", Usage: "Update function source code", Flags: []cli.Flag{fnNameFlag, fnNamespaceFlag, fnEnvNameFlag, envNamespaceFlag, fnCodeFlag, fnSrcArchiveFlag, fnDeployArchiveFlag, fnEntryPointFlag, fnPkgNameFlag, pkgNamespaceFlag, fnBuildCmdFlag, fnForceFlag, minCpu, maxCpu, minMem, maxMem, minScale, maxScale, fnExecutorTypeFlag, targetcpu}, Action: fnUpdate},
{Name: "delete", Usage: "Delete function", Flags: []cli.Flag{fnNameFlag, fnNamespaceFlag}, Action: fnDelete},
// TODO : for fnList, i feel like it's nice to allow --fns all, to list functions across all namespaces for cluster admins, although, this is against ns isolation.
// so, in the future, if we end up using kubeconfig in fission cli and enforcing rolebindings to be created for users by admins etc, we can add this option at the time.
{Name: "list", Usage: "List all functions in a namespace if specified, else, list functions across all namespaces", Flags: []cli.Flag{fnNamespaceFlag}, Action: fnList},
{Name: "logs", Usage: "Display function logs", Flags: []cli.Flag{fnNameFlag, fnNamespaceFlag, fnPodFlag, fnFollowFlag, fnDetailFlag, fnLogDBTypeFlag, fnLogCountFlag}, Action: fnLogs},
{Name: "test", Usage: "Test a function", Flags: []cli.Flag{fnNameFlag, fnNamespaceFlag, fnEnvNameFlag, fnCodeFlag, fnSrcArchiveFlag, htMethodFlag, fnBodyFlag, fnHeaderFlag}, Action: fnTest},
}
// httptriggers
htNameFlag := cli.StringFlag{Name: "name", Usage: "HTTP Trigger name"}
htFnNameFlag := cli.StringFlag{Name: "function", Usage: "Function name"}
htSubcommands := []cli.Command{
{Name: "create", Aliases: []string{"add"}, Usage: "Create HTTP trigger", Flags: []cli.Flag{htMethodFlag, htUrlFlag, htFnNameFlag, specSaveFlag}, Action: htCreate},
{Name: "create", Aliases: []string{"add"}, Usage: "Create HTTP trigger", Flags: []cli.Flag{htMethodFlag, htUrlFlag, htFnNameFlag, fnNameFlag, fnNamespaceFlag, specSaveFlag}, Action: htCreate},
{Name: "get", Usage: "Get HTTP trigger", Flags: []cli.Flag{htMethodFlag, htUrlFlag}, Action: htGet},
{Name: "update", Usage: "Update HTTP trigger", Flags: []cli.Flag{htNameFlag, htFnNameFlag}, Action: htUpdate},
{Name: "delete", Usage: "Delete HTTP trigger", Flags: []cli.Flag{htNameFlag}, Action: htDelete},
{Name: "list", Usage: "List HTTP triggers", Flags: []cli.Flag{}, Action: htList},
{Name: "update", Usage: "Update HTTP trigger", Flags: []cli.Flag{htNameFlag, triggerNamespaceFlag, htFnNameFlag}, Action: htUpdate},
{Name: "delete", Usage: "Delete HTTP trigger", Flags: []cli.Flag{htNameFlag, triggerNamespaceFlag}, Action: htDelete},
{Name: "list", Usage: "List HTTP triggers", Flags: []cli.Flag{triggerNamespaceFlag}, Action: htList},
}
// timetriggers
@@ -170,11 +177,11 @@ func main() {
ttCronFlag := cli.StringFlag{Name: "cron", Usage: "Time Trigger cron spec ('0 30 * * *', '@every 5m', '@hourly')"}
ttFnNameFlag := cli.StringFlag{Name: "function", Usage: "Function name"}
ttSubcommands := []cli.Command{
{Name: "create", Aliases: []string{"add"}, Usage: "Create Time trigger", Flags: []cli.Flag{ttNameFlag, ttFnNameFlag, ttCronFlag, specSaveFlag}, Action: ttCreate},
{Name: "get", Usage: "Get Time trigger", Flags: []cli.Flag{}, Action: ttGet},
{Name: "update", Usage: "Update Time trigger", Flags: []cli.Flag{ttNameFlag, ttCronFlag, ttFnNameFlag}, Action: ttUpdate},
{Name: "delete", Usage: "Delete Time trigger", Flags: []cli.Flag{ttNameFlag}, Action: ttDelete},
{Name: "list", Usage: "List Time triggers", Flags: []cli.Flag{}, Action: ttList},
{Name: "create", Aliases: []string{"add"}, Usage: "Create Time trigger", Flags: []cli.Flag{ttNameFlag, ttFnNameFlag, fnNamespaceFlag, ttCronFlag, specSaveFlag}, Action: ttCreate},
{Name: "get", Usage: "Get Time trigger", Flags: []cli.Flag{triggerNamespaceFlag}, Action: ttGet},
{Name: "update", Usage: "Update Time trigger", Flags: []cli.Flag{ttNameFlag, triggerNamespaceFlag, ttCronFlag, ttFnNameFlag}, Action: ttUpdate},
{Name: "delete", Usage: "Delete Time trigger", Flags: []cli.Flag{ttNameFlag, triggerNamespaceFlag}, Action: ttDelete},
{Name: "list", Usage: "List Time triggers", Flags: []cli.Flag{triggerNamespaceFlag}, Action: ttList},
}
// Message queue trigger
@@ -185,11 +192,11 @@ func main() {
mqtRespTopicFlag := cli.StringFlag{Name: "resptopic", Usage: "Topic that the function response is sent on (optional; response discarded if unspecified)"}
mqtMsgContentType := cli.StringFlag{Name: "contenttype, c", Value: "application/json", Usage: "Content type of messages that publish to the topic (optional)"}
mqtSubcommands := []cli.Command{
{Name: "create", Aliases: []string{"add"}, Usage: "Create Message queue trigger", Flags: []cli.Flag{mqtNameFlag, mqtFnNameFlag, mqtMQTypeFlag, mqtTopicFlag, mqtRespTopicFlag, mqtMsgContentType, specSaveFlag}, Action: mqtCreate},
{Name: "get", Usage: "Get message queue trigger", Flags: []cli.Flag{}, Action: mqtGet},
{Name: "update", Usage: "Update message queue trigger", Flags: []cli.Flag{mqtNameFlag, mqtTopicFlag, mqtRespTopicFlag, mqtFnNameFlag, mqtMsgContentType}, Action: mqtUpdate},
{Name: "delete", Usage: "Delete message queue trigger", Flags: []cli.Flag{mqtNameFlag}, Action: mqtDelete},
{Name: "list", Usage: "List message queue triggers", Flags: []cli.Flag{mqtMQTypeFlag}, Action: mqtList},
{Name: "create", Aliases: []string{"add"}, Usage: "Create Message queue trigger", Flags: []cli.Flag{mqtNameFlag, mqtFnNameFlag, fnNamespaceFlag, mqtMQTypeFlag, mqtTopicFlag, mqtRespTopicFlag, mqtMsgContentType, specSaveFlag}, Action: mqtCreate},
{Name: "get", Usage: "Get message queue trigger", Flags: []cli.Flag{triggerNamespaceFlag}, Action: mqtGet},
{Name: "update", Usage: "Update message queue trigger", Flags: []cli.Flag{mqtNameFlag, triggerNamespaceFlag, mqtTopicFlag, mqtRespTopicFlag, mqtFnNameFlag, mqtMsgContentType}, Action: mqtUpdate},
{Name: "delete", Usage: "Delete message queue trigger", Flags: []cli.Flag{mqtNameFlag, triggerNamespaceFlag}, Action: mqtDelete},
{Name: "list", Usage: "List message queue triggers", Flags: []cli.Flag{mqtMQTypeFlag, triggerNamespaceFlag}, Action: mqtList},
}
// environments
@@ -202,11 +209,11 @@ func main() {
envTerminationGracePeriodFlag := cli.Int64Flag{Name: "graceperiod, period", Value: 360, Usage: "The grace time (in seconds) for pod to perform connection draining before termination (optional)"}
envVersionFlag := cli.IntFlag{Name: "version", Value: 1, Usage: "Environment API version (1 means v1 interface)"}
envSubcommands := []cli.Command{
{Name: "create", Aliases: []string{"add"}, Usage: "Add an environment", Flags: []cli.Flag{envNameFlag, envPoolsizeFlag, envImageFlag, envBuilderImageFlag, envBuildCmdFlag, minCpu, maxCpu, minMem, maxMem, envVersionFlag, envExternalNetworkFlag, envTerminationGracePeriodFlag, specSaveFlag}, Action: envCreate},
{Name: "get", Usage: "Get environment details", Flags: []cli.Flag{envNameFlag}, Action: envGet},
{Name: "update", Usage: "Update environment", Flags: []cli.Flag{envNameFlag, envPoolsizeFlag, envImageFlag, envBuilderImageFlag, envBuildCmdFlag, minCpu, maxCpu, minMem, maxMem, envExternalNetworkFlag, envTerminationGracePeriodFlag}, Action: envUpdate},
{Name: "delete", Usage: "Delete environment", Flags: []cli.Flag{envNameFlag}, Action: envDelete},
{Name: "list", Usage: "List all environments", Flags: []cli.Flag{}, Action: envList},
{Name: "create", Aliases: []string{"add"}, Usage: "Add an environment", Flags: []cli.Flag{envNameFlag, envNamespaceFlag, envPoolsizeFlag, envImageFlag, envBuilderImageFlag, envBuildCmdFlag, minCpu, maxCpu, minMem, maxMem, envVersionFlag, envExternalNetworkFlag, envTerminationGracePeriodFlag, specSaveFlag}, Action: envCreate},
{Name: "get", Usage: "Get environment details", Flags: []cli.Flag{envNameFlag, envNamespaceFlag}, Action: envGet},
{Name: "update", Usage: "Update environment", Flags: []cli.Flag{envNameFlag, envNamespaceFlag, envPoolsizeFlag, envImageFlag, envBuilderImageFlag, envBuildCmdFlag, minCpu, maxCpu, minMem, maxMem, envExternalNetworkFlag, envTerminationGracePeriodFlag}, Action: envUpdate},
{Name: "delete", Usage: "Delete environment", Flags: []cli.Flag{envNameFlag, envNamespaceFlag}, Action: envDelete},
{Name: "list", Usage: "List all environments", Flags: []cli.Flag{envNamespaceFlag}, Action: envList},
}
// watches
@@ -216,11 +223,11 @@ func main() {
wObjTypeFlag := cli.StringFlag{Name: "type", Usage: "Type of resource to watch (Pod, Service, etc.)"}
wLabelsFlag := cli.StringFlag{Name: "labels", Usage: "Label selector of the form a=b,c=d"}
wSubCommands := []cli.Command{
{Name: "create", Aliases: []string{"add"}, Usage: "Create a watch", Flags: []cli.Flag{wFnNameFlag, wNamespaceFlag, wObjTypeFlag, wLabelsFlag, specSaveFlag}, Action: wCreate},
{Name: "get", Usage: "Get details about a watch", Flags: []cli.Flag{wNameFlag}, Action: wGet},
{Name: "create", Aliases: []string{"add"}, Usage: "Create a watch", Flags: []cli.Flag{wFnNameFlag, fnNamespaceFlag, wNamespaceFlag, wObjTypeFlag, wLabelsFlag, specSaveFlag}, Action: wCreate},
{Name: "get", Usage: "Get details about a watch", Flags: []cli.Flag{wNameFlag, triggerNamespaceFlag}, Action: wGet},
// TODO add update flag when supported
{Name: "delete", Usage: "Delete watch", Flags: []cli.Flag{wNameFlag}, Action: wDelete},
{Name: "list", Usage: "List all watches", Flags: []cli.Flag{}, Action: wList},
{Name: "delete", Usage: "Delete watch", Flags: []cli.Flag{wNameFlag, triggerNamespaceFlag}, Action: wDelete},
{Name: "list", Usage: "List all watches", Flags: []cli.Flag{triggerNamespaceFlag}, Action: wList},
}
// packages
@@ -233,13 +240,13 @@ func main() {
pkgOutputFlag := cli.StringFlag{Name: "output, o", Usage: "Output filename to save archive content"}
pkgOrphanFlag := cli.BoolFlag{Name: "orphan", Usage: "orphan packages that are not referenced by any function"}
pkgSubCommands := []cli.Command{
{Name: "create", Usage: "Create new package", Flags: []cli.Flag{pkgEnvironmentFlag, pkgSrcArchiveFlag, pkgDeployArchiveFlag, pkgBuildCmdFlag}, Action: pkgCreate},
{Name: "update", Usage: "Update package", Flags: []cli.Flag{pkgNameFlag, pkgEnvironmentFlag, pkgSrcArchiveFlag, pkgDeployArchiveFlag, pkgBuildCmdFlag, pkgForceFlag}, Action: pkgUpdate},
{Name: "getsrc", Usage: "Get source archive content", Flags: []cli.Flag{pkgNameFlag, pkgOutputFlag}, Action: pkgSourceGet},
{Name: "getdeploy", Usage: "Get deployment archive content", Flags: []cli.Flag{pkgNameFlag, pkgOutputFlag}, Action: pkgDeployGet},
{Name: "info", Usage: "Show package information", Flags: []cli.Flag{pkgNameFlag}, Action: pkgInfo},
{Name: "list", Usage: "List all packages", Flags: []cli.Flag{pkgOrphanFlag}, Action: pkgList},
{Name: "delete", Usage: "Delete package", Flags: []cli.Flag{pkgNameFlag, pkgForceFlag, pkgOrphanFlag}, Action: pkgDelete},
{Name: "create", Usage: "Create new package", Flags: []cli.Flag{pkgNamespaceFlag, pkgEnvironmentFlag, envNamespaceFlag, pkgSrcArchiveFlag, pkgDeployArchiveFlag, pkgBuildCmdFlag}, Action: pkgCreate},
{Name: "update", Usage: "Update package", Flags: []cli.Flag{pkgNameFlag, pkgNamespaceFlag, pkgEnvironmentFlag, envNamespaceFlag, pkgSrcArchiveFlag, pkgDeployArchiveFlag, pkgBuildCmdFlag, pkgForceFlag}, Action: pkgUpdate},
{Name: "getsrc", Usage: "Get source archive content", Flags: []cli.Flag{pkgNameFlag, pkgNamespaceFlag, pkgOutputFlag}, Action: pkgSourceGet},
{Name: "getdeploy", Usage: "Get deployment archive content", Flags: []cli.Flag{pkgNameFlag, pkgNamespaceFlag, pkgOutputFlag}, Action: pkgDeployGet},
{Name: "info", Usage: "Show package information", Flags: []cli.Flag{pkgNameFlag, pkgNamespaceFlag}, Action: pkgInfo},
{Name: "list", Usage: "List all packages", Flags: []cli.Flag{pkgOrphanFlag, pkgNamespaceFlag}, Action: pkgList},
{Name: "delete", Usage: "Delete package", Flags: []cli.Flag{pkgNameFlag, pkgNamespaceFlag, pkgForceFlag, pkgOrphanFlag}, Action: pkgDelete},
}
// upgrades, data migrations
+8 -7
View File
@@ -23,6 +23,7 @@ import (
"net/http"
"github.com/urfave/cli"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fission/fission"
"github.com/fission/fission/controller/client"
@@ -42,19 +43,19 @@ type (
)
func migrateDumpTPRResource(client *client.Client, filename string) {
pkgs, err := client.PackageList()
pkgs, err := client.PackageList(metav1.NamespaceAll)
checkErr(err, "dump packages")
fns, err := client.FunctionList()
fns, err := client.FunctionList(metav1.NamespaceAll)
checkErr(err, "dump functions")
httpTriggers, err := client.HTTPTriggerList()
httpTriggers, err := client.HTTPTriggerList(metav1.NamespaceAll)
checkErr(err, "dump http triggers")
envs, err := client.EnvironmentList()
envs, err := client.EnvironmentList(metav1.NamespaceAll)
checkErr(err, "dump environments")
watches, err := client.WatchList()
watches, err := client.WatchList(metav1.NamespaceAll)
checkErr(err, "dump watches")
timeTriggers, err := client.TimeTriggerList()
timeTriggers, err := client.TimeTriggerList(metav1.NamespaceAll)
checkErr(err, "dump time triggers")
mqTriggers, err := client.MessageQueueTriggerList(fission.MessageQueueTypeNats)
mqTriggers, err := client.MessageQueueTriggerList(fission.MessageQueueTypeNats, metav1.NamespaceAll)
checkErr(err, "dump message queue triggers")
tprResource := TPRResource{
+11 -4
View File
@@ -40,6 +40,7 @@ func mqtCreate(c *cli.Context) error {
if len(fnName) == 0 {
fatal("Need a function name to create a trigger, use --function")
}
fnNamespace := c.String("fnNamespace")
var mqType fission.MessageQueueType
switch c.String("mqtype") {
@@ -76,7 +77,7 @@ func mqtCreate(c *cli.Context) error {
mqt := &crd.MessageQueueTrigger{
Metadata: metav1.ObjectMeta{
Name: mqtName,
Namespace: metav1.NamespaceDefault,
Namespace: fnNamespace,
},
Spec: fission.MessageQueueTriggerSpec{
FunctionReference: fission.FunctionReference{
@@ -115,6 +116,8 @@ func mqtUpdate(c *cli.Context) error {
if len(mqtName) == 0 {
fatal("Need name of trigger, use --name")
}
mqtNs := c.String("triggerns")
topic := c.String("topic")
respTopic := c.String("resptopic")
fnName := c.String("function")
@@ -122,10 +125,12 @@ func mqtUpdate(c *cli.Context) error {
mqt, err := client.MessageQueueTriggerGet(&metav1.ObjectMeta{
Name: mqtName,
Namespace: metav1.NamespaceDefault,
Namespace: mqtNs,
})
checkErr(err, "get Time trigger")
// TODO : Find out if we can make a call to checkIfFunctionExists, in the same ns more importantly.
checkMQTopicAvailability(mqt.Spec.MessageQueueType, topic, respTopic)
updated := false
@@ -163,10 +168,11 @@ func mqtDelete(c *cli.Context) error {
if len(mqtName) == 0 {
fatal("Need name of trigger to delete, use --name")
}
mqtNs := c.String("triggerns")
err := client.MessageQueueTriggerDelete(&metav1.ObjectMeta{
Name: mqtName,
Namespace: metav1.NamespaceDefault,
Namespace: mqtNs,
})
checkErr(err, "delete trigger")
@@ -176,8 +182,9 @@ func mqtDelete(c *cli.Context) error {
func mqtList(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
mqtNs := c.String("triggerns")
mqts, err := client.MessageQueueTriggerList(c.String("mqtype"))
mqts, err := client.MessageQueueTriggerList(c.String("mqtype"), mqtNs)
checkErr(err, "list message queue triggers")
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
+32 -23
View File
@@ -33,8 +33,8 @@ import (
"github.com/fission/fission/crd"
)
func getFunctionsByPackage(client *client.Client, pkgName string) ([]crd.Function, error) {
fnList, err := client.FunctionList()
func getFunctionsByPackage(client *client.Client, pkgName, pkgNamespace string) ([]crd.Function, error) {
fnList, err := client.FunctionList(pkgNamespace)
if err != nil {
return nil, err
}
@@ -65,11 +65,12 @@ func downloadStoragesvcURL(client *client.Client, fileUrl string) io.ReadCloser
func pkgCreate(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
pkgNamespace := c.String("pkgNamespace")
envName := c.String("env")
if len(envName) == 0 {
fatal("Need --env argument.")
}
envNamespace := c.String("envNamespace")
srcArchiveName := c.String("src")
deployArchiveName := c.String("deploy")
buildcmd := c.String("buildcmd")
@@ -78,7 +79,7 @@ func pkgCreate(c *cli.Context) error {
fatal("Need --src to specify source archive, or use --deploy to specify deployment archive.")
}
meta := createPackage(client, envName, srcArchiveName, deployArchiveName, buildcmd, "")
meta := createPackage(client, pkgNamespace, envName, envNamespace, srcArchiveName, deployArchiveName, buildcmd, "")
fmt.Printf("Package '%v' created\n", meta.GetName())
return nil
@@ -91,9 +92,11 @@ func pkgUpdate(c *cli.Context) error {
if len(pkgName) == 0 {
fatal("Need --name argument.")
}
pkgNamespace := c.String("pkgNamespace")
force := c.Bool("f")
envName := c.String("env")
envNamespace := c.String("envNamespace")
srcArchiveName := c.String("src")
deployArchiveName := c.String("deploy")
buildcmd := c.String("buildcmd")
@@ -108,12 +111,12 @@ func pkgUpdate(c *cli.Context) error {
}
pkg, err := client.PackageGet(&metav1.ObjectMeta{
Namespace: metav1.NamespaceDefault,
Namespace: pkgNamespace,
Name: pkgName,
})
checkErr(err, "get package")
fnList, err := getFunctionsByPackage(client, pkg.Metadata.Name)
fnList, err := getFunctionsByPackage(client, pkg.Metadata.Name, pkg.Metadata.Namespace)
checkErr(err, "get function list")
if !force && len(fnList) > 1 {
@@ -121,7 +124,7 @@ func pkgUpdate(c *cli.Context) error {
}
newPkgMeta := updatePackage(client, pkg,
envName, srcArchiveName, deployArchiveName, buildcmd)
envName, envNamespace, srcArchiveName, deployArchiveName, buildcmd)
// update resource version of package reference of functions that shared the same package
for _, fn := range fnList {
@@ -135,7 +138,7 @@ func pkgUpdate(c *cli.Context) error {
return nil
}
func updatePackage(client *client.Client, pkg *crd.Package, envName,
func updatePackage(client *client.Client, pkg *crd.Package, envName, envNamespace,
srcArchiveName, deployArchiveName, buildcmd string) *metav1.ObjectMeta {
var srcArchiveMetadata, deployArchiveMetadata *fission.Archive
@@ -143,6 +146,7 @@ func updatePackage(client *client.Client, pkg *crd.Package, envName,
if len(envName) > 0 {
pkg.Spec.Environment.Name = envName
pkg.Spec.Environment.Namespace = envNamespace
needToBuild = true
}
@@ -183,11 +187,12 @@ func pkgSourceGet(c *cli.Context) error {
if len(pkgName) == 0 {
fatal("Need name of package, use --name")
}
pkgNamespace := c.String("pkgNamespace")
output := c.String("output")
pkg, err := client.PackageGet(&metav1.ObjectMeta{
Namespace: metav1.NamespaceDefault,
Namespace: pkgNamespace,
Name: pkgName,
})
if err != nil {
@@ -219,11 +224,12 @@ func pkgDeployGet(c *cli.Context) error {
if len(pkgName) == 0 {
fatal("Need name of package, use --name")
}
pkgNamespace := c.String("pkgNamespace")
output := c.String("output")
pkg, err := client.PackageGet(&metav1.ObjectMeta{
Namespace: metav1.NamespaceDefault,
Namespace: pkgNamespace,
Name: pkgName,
})
if err != nil {
@@ -255,9 +261,10 @@ func pkgInfo(c *cli.Context) error {
if len(pkgName) == 0 {
fatal("Need name of package, use --name")
}
pkgNamespace := c.String("pkgNamespace")
pkg, err := client.PackageGet(&metav1.ObjectMeta{
Namespace: metav1.NamespaceDefault,
Namespace: pkgNamespace,
Name: pkgName,
})
if err != nil {
@@ -278,8 +285,9 @@ func pkgList(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
// option for the user to list all orphan packages (not referenced by any function)
listOrphans := c.Bool("orphan")
pkgNamespace := c.String("pkgNamespace")
pkgList, err := client.PackageList()
pkgList, err := client.PackageList(pkgNamespace)
if err != nil {
return err
}
@@ -288,7 +296,7 @@ func pkgList(c *cli.Context) error {
fmt.Fprintf(w, "%v\t%v\t%v\n", "NAME", "BUILD_STATUS", "ENV")
if listOrphans {
for _, pkg := range pkgList {
fnList, err := getFunctionsByPackage(client, pkg.Metadata.Name)
fnList, err := getFunctionsByPackage(client, pkg.Metadata.Name, pkg.Metadata.Namespace)
checkErr(err, fmt.Sprintf("get functions sharing package %s", pkg.Metadata.Name))
if len(fnList) == 0 {
fmt.Fprintf(w, "%v\t%v\t%v\n", pkg.Metadata.Name, pkg.Status.BuildStatus, pkg.Spec.Environment.Name)
@@ -306,18 +314,18 @@ func pkgList(c *cli.Context) error {
return nil
}
func deleteOrphanPkgs(client *client.Client) error {
pkgList, err := client.PackageList()
func deleteOrphanPkgs(client *client.Client, pkgNamespace string) error {
pkgList, err := client.PackageList(pkgNamespace)
if err != nil {
return err
}
// range through all packages and find out the ones not referenced by any function
for _, pkg := range pkgList {
fnList, err := getFunctionsByPackage(client, pkg.Metadata.Name)
fnList, err := getFunctionsByPackage(client, pkg.Metadata.Name, pkgNamespace)
checkErr(err, fmt.Sprintf("get functions sharing package %s", pkg.Metadata.Name))
if len(fnList) == 0 {
err = deletePackage(client, pkg.Metadata.Name)
err = deletePackage(client, pkg.Metadata.Name, pkgNamespace)
if err != nil {
return err
}
@@ -326,9 +334,9 @@ func deleteOrphanPkgs(client *client.Client) error {
return nil
}
func deletePackage(client *client.Client, pkgName string) error {
func deletePackage(client *client.Client, pkgName string, pkgNamespace string) error {
return client.PackageDelete(&metav1.ObjectMeta{
Namespace: metav1.NamespaceDefault,
Namespace: pkgNamespace,
Name: pkgName,
})
}
@@ -337,6 +345,7 @@ func pkgDelete(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
pkgName := c.String("name")
pkgNamespace := c.String("pkgNamespace")
deleteOrphans := c.Bool("orphan")
if len(pkgName) == 0 && !deleteOrphans {
@@ -352,25 +361,25 @@ func pkgDelete(c *cli.Context) error {
force := c.Bool("f")
_, err := client.PackageGet(&metav1.ObjectMeta{
Namespace: metav1.NamespaceDefault,
Namespace: pkgNamespace,
Name: pkgName,
})
checkErr(err, "find package")
fnList, err := getFunctionsByPackage(client, pkgName)
fnList, err := getFunctionsByPackage(client, pkgName, pkgNamespace)
if !force && len(fnList) > 0 {
fatal("Package is used by at least one function, use -f to force delete")
}
err = deletePackage(client, pkgName)
err = deletePackage(client, pkgName, pkgNamespace)
if err != nil {
return err
}
fmt.Printf("Package '%v' deleted\n", pkgName)
} else {
err := deleteOrphanPkgs(client)
err := deleteOrphanPkgs(client, pkgNamespace)
checkErr(err, "error deleting orphan packages")
fmt.Println("Orphan packages deleted")
}
+30 -11
View File
@@ -242,7 +242,8 @@ func (fr *FissionResources) validate() error {
// check references: both dangling refs + garbage
// packages -> archives
// functions -> packages
// functions -> environments [TODO]
// functions -> environments + shared environments between functions [TODO]
// functions -> secrets + configmaps (same ns) [TODO]
// triggers -> functions
// index archives
@@ -301,12 +302,30 @@ func (fr *FissionResources) validate() error {
for _, f := range fr.functions {
functions[mapKey(&f.Metadata)] = false
// check package ref from function
pkgMeta := &metav1.ObjectMeta{
Name: f.Spec.Package.PackageRef.Name,
Namespace: f.Spec.Package.PackageRef.Namespace,
}
if _, ok := packages[mapKey(pkgMeta)]; !ok {
// check package ref from function
packageRefExists := func() bool {
_, ok := packages[mapKey(pkgMeta)]
return ok
}
// check that the package referenced by each function is in the same ns as the function
packageRefInFuncNs := func(f *crd.Function) bool {
return f.Spec.Package.PackageRef.Namespace == f.Metadata.Namespace
}
if !packageRefInFuncNs(&f) {
result = multierror.Append(result, fmt.Errorf(
"%v: function '%v' references a package outside of its namespace %v/%v",
fr.sourceMap.locations["Function"][f.Metadata.Namespace][f.Metadata.Name],
f.Metadata.Name,
f.Spec.Package.PackageRef.Namespace,
f.Spec.Package.PackageRef.Name))
} else if !packageRefExists() {
result = multierror.Append(result, fmt.Errorf(
"%v: function '%v' references unknown package %v/%v",
fr.sourceMap.locations["Function"][f.Metadata.Namespace][f.Metadata.Name],
@@ -781,7 +800,7 @@ func applyArchives(fclient *client.Client, specDir string, fr *FissionResources)
// get list of packages, make content-indexed map of available archives
availableArchives := make(map[string]string) // (sha256 -> url)
pkgs, err := fclient.PackageList()
pkgs, err := fclient.PackageList(metav1.NamespaceAll)
if err != nil {
return err
}
@@ -1031,7 +1050,7 @@ func waitForPackageBuild(fclient *client.Client, pkg *crd.Package) (*crd.Package
func applyPackages(fclient *client.Client, fr *FissionResources, delete bool) (map[string]metav1.ObjectMeta, *resourceApplyStatus, error) {
// get list
allObjs, err := fclient.PackageList()
allObjs, err := fclient.PackageList(metav1.NamespaceAll)
if err != nil {
return nil, nil, err
}
@@ -1136,7 +1155,7 @@ func applyPackages(fclient *client.Client, fr *FissionResources, delete bool) (m
func applyFunctions(fclient *client.Client, fr *FissionResources, delete bool) (map[string]metav1.ObjectMeta, *resourceApplyStatus, error) {
// get list
allObjs, err := fclient.FunctionList()
allObjs, err := fclient.FunctionList(metav1.NamespaceAll)
if err != nil {
return nil, nil, err
}
@@ -1219,7 +1238,7 @@ func applyFunctions(fclient *client.Client, fr *FissionResources, delete bool) (
func applyEnvironments(fclient *client.Client, fr *FissionResources, delete bool) (map[string]metav1.ObjectMeta, *resourceApplyStatus, error) {
// get list
allObjs, err := fclient.EnvironmentList()
allObjs, err := fclient.EnvironmentList(metav1.NamespaceAll)
if err != nil {
return nil, nil, err
}
@@ -1302,7 +1321,7 @@ func applyEnvironments(fclient *client.Client, fr *FissionResources, delete bool
func applyHTTPTriggers(fclient *client.Client, fr *FissionResources, delete bool) (map[string]metav1.ObjectMeta, *resourceApplyStatus, error) {
// get list
allObjs, err := fclient.HTTPTriggerList()
allObjs, err := fclient.HTTPTriggerList(metav1.NamespaceAll)
if err != nil {
return nil, nil, err
}
@@ -1385,7 +1404,7 @@ func applyHTTPTriggers(fclient *client.Client, fr *FissionResources, delete bool
func applyKubernetesWatchTriggers(fclient *client.Client, fr *FissionResources, delete bool) (map[string]metav1.ObjectMeta, *resourceApplyStatus, error) {
// get list
allObjs, err := fclient.WatchList()
allObjs, err := fclient.WatchList(metav1.NamespaceAll)
if err != nil {
return nil, nil, err
}
@@ -1468,7 +1487,7 @@ func applyKubernetesWatchTriggers(fclient *client.Client, fr *FissionResources,
func applyTimeTriggers(fclient *client.Client, fr *FissionResources, delete bool) (map[string]metav1.ObjectMeta, *resourceApplyStatus, error) {
// get list
allObjs, err := fclient.TimeTriggerList()
allObjs, err := fclient.TimeTriggerList(metav1.NamespaceAll)
if err != nil {
return nil, nil, err
}
@@ -1551,7 +1570,7 @@ func applyTimeTriggers(fclient *client.Client, fr *FissionResources, delete bool
func applyMessageQueueTriggers(fclient *client.Client, fr *FissionResources, delete bool) (map[string]metav1.ObjectMeta, *resourceApplyStatus, error) {
// get list
allObjs, err := fclient.MessageQueueTriggerList("")
allObjs, err := fclient.MessageQueueTriggerList("", metav1.NamespaceAll)
if err != nil {
return nil, nil, err
}
+13 -4
View File
@@ -40,6 +40,8 @@ func ttCreate(c *cli.Context) error {
if len(fnName) == 0 {
fatal("Need a function name to create a trigger, use --function")
}
fnNamespace := c.String("fnNamespace")
cron := c.String("cron")
if len(cron) == 0 {
fatal("Need a cron spec like '0 30 * * *', '@every 1h30m', or '@hourly'; use --cron")
@@ -48,7 +50,7 @@ func ttCreate(c *cli.Context) error {
tt := &crd.TimeTrigger{
Metadata: metav1.ObjectMeta{
Name: name,
Namespace: metav1.NamespaceDefault,
Namespace: fnNamespace,
},
Spec: fission.TimeTriggerSpec{
Cron: cron,
@@ -84,10 +86,11 @@ func ttUpdate(c *cli.Context) error {
if len(ttName) == 0 {
fatal("Need name of trigger, use --name")
}
ttNs := c.String("triggerns")
tt, err := client.TimeTriggerGet(&metav1.ObjectMeta{
Name: ttName,
Namespace: metav1.NamespaceDefault,
Namespace: ttNs,
})
checkErr(err, "get time trigger")
@@ -97,6 +100,10 @@ func ttUpdate(c *cli.Context) error {
tt.Spec.Cron = newCron
updated = true
}
// TODO : During update, function has to be in the same ns as the trigger object
// but since we are not checking this for other triggers too, not sure if we need a check here.
fnName := c.String("function")
if len(fnName) > 0 {
tt.Spec.FunctionReference.Name = fnName
@@ -120,10 +127,11 @@ func ttDelete(c *cli.Context) error {
if len(ttName) == 0 {
fatal("Need name of trigger to delete, use --name")
}
ttNs := c.String("triggerns")
err := client.TimeTriggerDelete(&metav1.ObjectMeta{
Name: ttName,
Namespace: metav1.NamespaceDefault,
Namespace: ttNs,
})
checkErr(err, "delete trigger")
@@ -133,8 +141,9 @@ func ttDelete(c *cli.Context) error {
func ttList(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
ttNs := c.String("triggerns")
tts, err := client.TimeTriggerList()
tts, err := client.TimeTriggerList(ttNs)
checkErr(err, "list Time triggers")
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
+7 -3
View File
@@ -36,6 +36,7 @@ func wCreate(c *cli.Context) error {
if len(fnName) == 0 {
fatal("Need a function name to create a watch, use --function")
}
fnNamespace := c.String("fnNamespace")
namespace := c.String("ns")
if len(namespace) == 0 {
@@ -64,7 +65,7 @@ func wCreate(c *cli.Context) error {
w := &crd.KubernetesWatchTrigger{
Metadata: metav1.ObjectMeta{
Name: watchName,
Namespace: metav1.NamespaceDefault,
Namespace: fnNamespace,
},
Spec: fission.KubernetesWatchTriggerSpec{
Namespace: namespace,
@@ -111,10 +112,11 @@ func wDelete(c *cli.Context) error {
if len(wName) == 0 {
fatal("Need name of watch to delete, use --name")
}
wNs := c.String("triggerns")
err := client.WatchDelete(&metav1.ObjectMeta{
Name: wName,
Namespace: metav1.NamespaceDefault,
Namespace: wNs,
})
checkErr(err, "delete watch")
@@ -125,7 +127,9 @@ func wDelete(c *cli.Context) error {
func wList(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
ws, err := client.WatchList()
wNs := c.String("triggerns")
ws, err := client.WatchList(wNs)
checkErr(err, "list watches")
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
+4 -1
View File
@@ -327,7 +327,10 @@ func (ws *watchSubscription) eventDispatchLoop() {
continue
}
url := fission.UrlForFunction(ws.watch.Spec.FunctionReference.Name)
// with the addition of multi-tenancy, the users can create functions in any namespace. however,
// the triggers can only be created in the same namespace as the function.
// so essentially, function namespace = trigger namespace.
url := fission.UrlForFunction(ws.watch.Spec.FunctionReference.Name, ws.watch.Metadata.Namespace)
ws.publisher.Publish(buf.String(), headers, url)
}
}
+7 -4
View File
@@ -210,10 +210,13 @@ func (asc AzureStorageConnection) subscribe(trigger *crd.MessageQueueTrigger) (m
queue: asc.service.GetQueue(trigger.Spec.Topic),
queueName: trigger.Spec.Topic,
outputQueueName: trigger.Spec.ResponseTopic,
functionURL: asc.routerURL + "/" + strings.TrimPrefix(fission.UrlForFunction(trigger.Spec.FunctionReference.Name), "/"),
contentType: trigger.Spec.ContentType,
unsubscribe: make(chan bool),
done: make(chan bool),
// with the addition of multi-tenancy, the users can create functions in any namespace. however,
// the triggers can only be created in the same namespace as the function.
// so essentially, function namespace = trigger namespace.
functionURL: asc.routerURL + "/" + strings.TrimPrefix(fission.UrlForFunction(trigger.Spec.FunctionReference.Name, trigger.Metadata.Namespace), "/"),
contentType: trigger.Spec.ContentType,
unsubscribe: make(chan bool),
done: make(chan bool),
}
go runAzureQueueSubscription(asc, subscription)
+4 -2
View File
@@ -284,7 +284,8 @@ func TestAzureStorageQueuePoisonMessage(t *testing.T) {
}
subscription, err := connection.subscribe(&crd.MessageQueueTrigger{
Metadata: metav1.ObjectMeta{
Name: TriggerName,
Name: TriggerName,
Namespace: metav1.NamespaceDefault,
},
Spec: fission.MessageQueueTriggerSpec{
FunctionReference: fission.FunctionReference{
@@ -427,7 +428,8 @@ func runAzureStorageQueueTest(t *testing.T, count int, output bool) {
}
subscription, err := connection.subscribe(&crd.MessageQueueTrigger{
Metadata: metav1.ObjectMeta{
Name: TriggerName,
Name: TriggerName,
Namespace: metav1.NamespaceDefault,
},
Spec: fission.MessageQueueTriggerSpec{
FunctionReference: fission.FunctionReference{
+4 -1
View File
@@ -100,7 +100,10 @@ func msgHandler(nats *Nats, trigger *crd.MessageQueueTrigger) func(*ns.Msg) {
trigger.Spec.FunctionReference.Type, trigger.Metadata.Name)
}
url := nats.routerUrl + "/" + strings.TrimPrefix(fission.UrlForFunction(trigger.Spec.FunctionReference.Name), "/")
// with the addition of multi-tenancy, the users can create functions in any namespace. however,
// the triggers can only be created in the same namespace as the function.
// so essentially, function namespace = trigger namespace.
url := nats.routerUrl + "/" + strings.TrimPrefix(fission.UrlForFunction(trigger.Spec.FunctionReference.Name, trigger.Metadata.Namespace), "/")
log.Printf("Making HTTP request to %v", url)
headers := map[string]string{
+1
View File
@@ -57,6 +57,7 @@ func MakeWebhookPublisher(baseUrl string) *WebhookPublisher {
}
func (p *WebhookPublisher) Publish(body string, headers map[string]string, target string) {
// serializing the request gives user a guarantee that the request is sent in sequence order
p.requestChannel <- &publishRequest{
body: body,
headers: headers,
+3 -3
View File
@@ -152,7 +152,7 @@ func (ts *HTTPTriggerSet) getRouter() *mux.Router {
function: &m,
executor: ts.executor,
}
muxRouter.HandleFunc(fission.UrlForFunction(function.Metadata.Name), fh.handler)
muxRouter.HandleFunc(fission.UrlForFunction(function.Metadata.Name, function.Metadata.Namespace), fh.handler)
}
// Healthz endpoint for the router.
@@ -167,7 +167,7 @@ func (ts *HTTPTriggerSet) updateTriggerStatusFailed(ht *crd.HTTPTrigger, err err
func (ts *HTTPTriggerSet) initTriggerController() (k8sCache.Store, k8sCache.Controller) {
resyncPeriod := 30 * time.Second
listWatch := k8sCache.NewListWatchFromClient(ts.crdClient, "httptriggers", metav1.NamespaceDefault, fields.Everything())
listWatch := k8sCache.NewListWatchFromClient(ts.crdClient, "httptriggers", metav1.NamespaceAll, fields.Everything())
store, controller := k8sCache.NewInformer(listWatch, &crd.HTTPTrigger{}, resyncPeriod,
k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
@@ -185,7 +185,7 @@ func (ts *HTTPTriggerSet) initTriggerController() (k8sCache.Store, k8sCache.Cont
func (ts *HTTPTriggerSet) initFunctionController() (k8sCache.Store, k8sCache.Controller) {
resyncPeriod := 30 * time.Second
listWatch := k8sCache.NewListWatchFromClient(ts.crdClient, "functions", metav1.NamespaceDefault, fields.Everything())
listWatch := k8sCache.NewListWatchFromClient(ts.crdClient, "functions", metav1.NamespaceAll, fields.Everything())
store, controller := k8sCache.NewInformer(listWatch, &crd.Function{}, resyncPeriod,
k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
-1
View File
@@ -1,7 +1,6 @@
#!/bin/bash
set -euo pipefail
ROOT=$(dirname $0)/../..
fn=nodejs-hello-$(date +%s)
+275
View File
@@ -0,0 +1,275 @@
#!/bin/bash
# we may not need this to run as a pre-check-in test for every PR. but only once in a while to ensure nothing's broken.
set -euo pipefail
id=""
ROOT=$(dirname $0)/../..
final_cleanup() {
rm -rf testDir1 || true
kubectl delete ns "ns1-$id" "ns2-$id" || true
}
cleanup() {
[[ -n "${1+x}" && -n "${2+x}" ]]; fission env delete --name $1 --envns $2 || true
[[ -n "${3+x}" && -n "${4+x}" ]]; fission fn delete --name $3 --fns $4 || true
[[ -n "${5+x}" && -n "${6+x}" ]]; fission pkg delete --name $5 --pkgns $6 || true
[[ -n "${7+x}" && -n "${8+x}" ]]; fission route delete --name $7 --triggerns $8 || true
}
create_python_source_code() {
mkdir testDir1
printf 'def main():\n return "Hello, world!"' > testDir1/hello.py
}
verify_pod_ns() {
kubectl get deployment -n $2 | grep $1
}
verify_function_pod_ns() {
function_label=$1
env_ns=$2
kubectl get pods -n $env_ns -L functionName| grep $function_label
}
verify_obj_in_ns() {
obj_kind=$1
obj_name=$2
obj_ns=$3
kubectl get $obj_kind $obj_name -n $obj_ns
}
get_pkg_name_from_func() {
pkg=`kubectl get function $1 -n $2 -o jsonpath='{.spec.package.packageref.name}'`
echo "$pkg"
}
# since we havent deleted the funcs created previously in new_deploy_mgr tests,
# just curl on the funcs and verifying http response code should be enough
internal_route_test_2() {
http_status=`curl -sw "%{http_code}" http://$FISSION_ROUTER/fission-function/func5 -o /tmp/file`
echo "http_status : $http_status"
[[ "$http_status" -eq "200" ]]
}
internal_route_test_1() {
ns="ns2-$id"
http_status=`curl -sw "%{http_code}" http://$FISSION_ROUTER/fission-function/$ns/func4 -o /tmp/file`
echo "http_status : $http_status"
[[ "$http_status" -eq "200" ]]
}
new_deploy_mgr_and_internal_route_test_2() {
log "Starting new_deploy_mgr_and_internal_route_test_1 with env in default ns"
fission env create --name python --image fission/python-env
fission fn create --name func5 --env python --code testDir1/hello.py --minscale 1 --maxscale 4 --executortype newdeploy
sleep 15
# function is loaded in $FISSION_NAMESPACE because func object was created in default ns
verify_function_pod_ns func5 "$FUNCTION_NAMESPACE" || (log "func func5 not specialized in $FUNCTION_NAMESPACE" &&
cleanup python "default" func5 "default" "" "" "" "" && exit 1)
internal_route_test_2 || ( log "internal route test for http://$FISSION_ROUTER/fission-function/func5 returned http_status: $http_status" &&
cleanup python "default" func5 "default" "" "" "" "" && exit 1)
cleanup python "default" func5 "default" "" "" "" ""
}
new_deploy_mgr_and_internal_route_test_1() {
log "Starting new_deploy_mgr_and_internal_route_test_1 with env and fn in different ns"
fission env create --name python --image fission/python-env --envns "ns1-$id"
fission fn create --name func4 --fns "ns2-$id" --env python --envns "ns1-$id" --code testDir1/hello.py --minscale 1 --maxscale 4 --executortype newdeploy
sleep 15
# note that this test is diff from pool_mgr test because here function is loaded in func ns and not in env ns
verify_function_pod_ns func4 "ns2-$id" || (log "func func4 not specialized in ns2-$id" &&
cleanup python "ns1-$id" func4 "ns2-$id" "" "" "" "" && exit 1)
internal_route_test_1 || (log "internal route test for http://$FISSION_ROUTER/fission-function/ns2-$id/func4 returned http_status: $http_status" &&
cleanup python "ns1-$id" func4 "ns2-$id" "" "" "" "" && exit 1)
cleanup python "ns1-$id" func4 "ns2-$id" "" "" "" ""
}
builder_mgr_test_2() {
log "Starting builder_mgr_test_2 with env in default ns"
fission env create --name python-builder-env --builder fission/python-builder --image fission/python-env
sleep 180
# verify the env builder pod came up in fission-builder and env runtime pod came up in fission-function ns
verify_pod_ns python-builder-env fission-builder || (log "python-builder-env builder env not found in fission-builder ns" &&
cleanup python-builder-env "default" "" "" "" "" "" "" && exit 1)
verify_pod_ns python-builder-env $FUNCTION_NAMESPACE || (log "python-builder-env runtime env not found in fission-function ns" &&
cleanup python-builder-env "default" "" "" "" "" "" "" && exit 1)
zip -jr src-pkg.zip $ROOT/examples/python/sourcepkg/
pkg=$(fission package create --src src-pkg.zip --env python-builder-env --buildcmd "./build.sh" --pkgns "ns2-$id"| cut -f2 -d' '| tr -d \')
sleep 60
fission fn create --name func4 --fns "ns2-$id" --pkg $pkg --entrypoint "user.main"
ht=$(fission route create --function func4 --fns "ns2-$id" --url /func4 --method GET | cut -f2 -d' '| tr -d \')
# get the function loaded into a pod
sleep 10
response=$(curl http://$FISSION_ROUTER/func4)
echo $response
echo $response | grep -i "a: 1 b: {c: 3, d: 4}" || (log "response a: 1 b: {c: 3, d: 4} not received" &&
cleanup python-builder-env "default" func4 "ns2-$id" "$pkg" "ns2-$id" $ht "ns2-$id" && exit 1)
# verify the function specialized pod is in fission-function. This also verifies builder ran successfully and in fission-builder
verify_function_pod_ns func4 $FUNCTION_NAMESPACE || (log "func func4 not specialized in $FUNCTION_NAMESPACE " &&
cleanup python-builder-env "default" func4 "ns2-$id" "$pkg" "ns2-$id" $ht "ns2-$id" && exit 1)
cleanup python-builder-env "default" func4 "ns2-$id" "$pkg" "ns2-$id" $ht "ns2-$id"
}
builder_mgr_test_1() {
log "Starting builder_mgr_test_1 with env and fn in different ns"
fission env create --name python-builder-env --envns "ns1-$id" --builder fission/python-builder --image fission/python-env
# we need to wait sufficiently for env pods to be up
sleep 180
zip -jr src-pkg.zip $ROOT/examples/python/sourcepkg/
pkg=$(fission package create --src src-pkg.zip --env python-builder-env --envns "ns1-$id" --buildcmd "./build.sh" --pkgns "ns2-$id"| cut -f2 -d' '| tr -d \')
sleep 60
fission fn create --name func3 --fns "ns2-$id" --pkg $pkg --entrypoint "user.main"
ht=$(fission route create --function func3 --fns "ns2-$id" --url /func3 --method GET | cut -f2 -d' '| tr -d \')
# get the function loaded into a pod
sleep 10
response=$(curl http://$FISSION_ROUTER/func3)
echo "response : $response"
echo $response | grep -i "a: 1 b: {c: 3, d: 4}" || (log "response a: 1 b: {c: 3, d: 4} not received" &&
cleanup python-builder-env "ns1-$id" func3 "ns2-$id" "$pkg" "ns2-$id" $ht "ns2-$id" && exit 1)
# verify the function specialized pod is in ns1-$id. This also verifies builder ran successfully and in ns1-$id
verify_function_pod_ns func3 "ns1-$id" || (log "func func3 not specialized in ns1-$id" &&
cleanup python-builder-env "ns1-$id" func3 "ns2-$id" "$pkg" "ns2-$id" $ht "ns2-$id" && exit 1)
cleanup python-builder-env "ns1-$id" func3 "ns2-$id" "$pkg" "ns2-$id" $ht "ns2-$id"
}
pool_mgr_test_2() {
log "Starting pool_mgr_test_2 with env in default ns"
fission env delete --name python || true
fission env create --name python --image fission/python-env
fission fn create --name func2 --fns "ns2-$id" --env python --code testDir1/hello.py
ht=$(fission route create --function func2 --fns "ns2-$id" --url /func2 | cut -f2 -d' '| tr -d \')
# verify that env object is created in default ns when envns option is absent with fission env create command
verify_obj_in_ns environment python default|| (log "env python not found in default ns" &&
cleanup python "default" func2 "ns2-$id" "" "" $ht "ns2-$id" && exit 1)
sleep 3
# get the function loaded into a pod
response=$(curl http://$FISSION_ROUTER/func2)
echo $response | grep "Hello" || (log "response Hello not received" &&
cleanup python "default" func2 "ns2-$id" "" "" $ht "ns2-$id" && exit 1)
# note that the env pod is created in the $FUNCTION_NAMESPACE ns though the env object is created in default ns
# so even if the function is created in a different ns, they will be loaded in the $FUNCTION_NAMESPACE.
verify_function_pod_ns func2 $FUNCTION_NAMESPACE || (log "func func2 not specialized in $FUNCTION_NAMESPACE" &&
cleanup python "default" func2 "ns2-$id" "" "" $ht "ns2-$id" && exit 1)
cleanup python "default" func2 "ns2-$id" "" "" $ht "ns2-$id"
}
pool_mgr_test_1() {
log "Starting pool_mgr_test_1 with env and fn in different ns"
fission env create --name python --image fission/python-env --envns "ns1-$id"
fission fn create --name func1 --fns "ns2-$id" --env python --envns "ns1-$id" --code testDir1/hello.py
ht=$(fission route create --function func1 --fns "ns2-$id" --url /func1 | cut -f2 -d' '| tr -d \')
# verify that fission objects are created in the expected namespaces
verify_obj_in_ns environment python "ns1-$id" || (log "env python not found in ns1-$id" &&
cleanup python "ns1-$id" && exit 1)
verify_obj_in_ns "function" func1 "ns2-$id" || (log "function func1 not found in ns2-$id" &&
cleanup python "ns1-$id" func1 "ns2-$id" && exit 1)
pkg=$( get_pkg_name_from_func func1 "ns2-$id" )
verify_obj_in_ns package $pkg "ns2-$id" || (log "package $pkg not found in ns2-$id" &&
cleanup python "ns1-$id" func1 "ns2-$id" $pkg "ns2-$id" && exit 1)
verify_obj_in_ns httptrigger $ht "ns2-$id" || (log "http trigger $ht not found in ns2-$id" &&
cleanup python "ns1-$id" func1 "ns2-$id" $pkg "ns2-$id" $ht "ns2-$id" && exit 1)
sleep 3
# get the function loaded into a pod
response=$(curl http://$FISSION_ROUTER/func1)
echo $response | grep "Hello" || (log "response Hello not received" &&
cleanup python "ns1-$id" func1 "ns2-$id" $pkg "ns2-$id" $ht "ns2-$id" && exit 1)
# note that the env pod is created in the ns that env is created
# so even if the function is created in a different ns, they will be loaded in the env pods ns.
# this behavior is for poolmgr so that functions can utilize the resources better.
verify_function_pod_ns func1 "ns1-$id" || (log "func func1 not specialized in ns1-$id" &&
cleanup python "ns1-$id" func1 "ns2-$id" $pkg "ns2-$id" $ht "ns2-$id" && exit 1)
cleanup python "ns1-$id" func1 "ns2-$id" $pkg "ns2-$id" $ht "ns2-$id"
}
main() {
# extract the test-id generated for this CI test run, so that they can be suffixed to namespaces created as part of
# this test and namespaces wont clash when fission CI tests are run in parallel in the future.
id=`echo $FISSION_NAMESPACE| cut -d"-" -f2`
echo "test_id : $id"
# create source code
create_python_source_code
# pool mgr tests
# 1. env ns1, func ns2 with code, route and verify specialized pod in ns1, also verify pkg in ns2
pool_mgr_test_1
# 3. env default, func with code in ns2, route and verify specialized pod in $FUNCTION_NAMESPACE.
# this test is to verify backward compatibility
pool_mgr_test_2
# builder mgr tests
# 1. env with builder image and runtime image in ns1, src pkg and func in ns2, route and verify specialized pod in ns1
builder_mgr_test_1
# 2. env with builder image and runtime image in default, src pkg and func in ns2,
# route and verify specialized pod in fission-builder. this test is to verify backward compatibility
builder_mgr_test_2
# new deploy mgr tests
# 1. env ns1, func ns2 with code, route and verify specialized pod in ns2,
new_deploy_mgr_and_internal_route_test_1
# 2. env default, func ns2 with code, route and verify specialized pod in fission-function
new_deploy_mgr_and_internal_route_test_2
# internal route tests. ( combined this with new deploy mgr tests, so we dont have to recreate envs and funcs again )
# 1. env ns1, func ns2 with code, curl http://FISSION_ROUTER/fission-function/ns2/func -> should work
# 2. env in default, func ns2 with code, curl http://FISSION_ROUTER/fission-function/func -> should work
# TODO : add following tests
# timer trigger tests.
# 1. env ns1, func ns2 with code, tt ( with a one time cron string for executing imm'ly), verify function is executed
# this also indirectly tests internal route established at http://FISSION_ROUTER/ns1/func
# 2. env in default, func ns2 with code, tt ( with a one time cron string for executing imm'ly), verify function is executed
# this also indirectly tests internal route established at http://FISSION_ROUTER/func
# kube watch tests.
# 1. env ns1, func ns2 with code, watch Trigger ( TBD), verify function is executed
# this also indirectly tests internal route established at http://FISSION_ROUTER/ns1/func
# 2. env in default, func ns2 with code, watch Trigger ( TBD ), verify function is executed
# this also indirectly tests internal route established at http://FISSION_ROUTER/func
# msq trigger tests.
# integrate after mqtrigger tests are checked into master.
final_cleanup
}
main
+1 -1
View File
@@ -2,7 +2,7 @@
set -euo pipefail
# Use package command to create two packages one with source
# Use package command to create two packages one with source
# archive and the other with deploy archive. Also, create a
# function to test the packages created by package command are
# able to work.
-1
View File
@@ -1,7 +1,6 @@
#!/bin/bash
set -euo pipefail
# This doesn't test fission, just the test framework. It ensures we
# have the right environment, that's all.
@@ -1,7 +1,6 @@
#!/bin/bash
set -euo pipefail
# 1. This test first creates a python function with a route
# 2. Makes a curl request to the route and verifies http.StatusOK is received.
# This step ensures the pod address is cached in router.
+10 -20
View File
@@ -87,17 +87,10 @@ func (timer *Timer) svc() {
func (timer *Timer) syncCron(triggers []crd.TimeTrigger) error {
// add new triggers or update existing ones
triggerMap := make(map[string]bool)
for _, t := range triggers {
if item, ok := timer.triggers[t.Metadata.Name]; ok {
// the item exists, update the item if needed
// if both UID and ResourceVersion match, the
// two triggers are identical
if item.trigger.Metadata.UID == t.Metadata.UID &&
item.trigger.Metadata.ResourceVersion == t.Metadata.ResourceVersion {
continue
}
triggerMap[crd.CacheKey(&t.Metadata)] = true
if item, ok := timer.triggers[crd.CacheKey(&t.Metadata)]; ok {
// update cron if the cron spec changed
if item.trigger.Spec.Cron != t.Spec.Cron {
// if there is an cron running, stop it
@@ -109,7 +102,7 @@ func (timer *Timer) syncCron(triggers []crd.TimeTrigger) error {
item.trigger = t
} else {
timer.triggers[t.Metadata.Name] = &timerTriggerWithCron{
timer.triggers[crd.CacheKey(&t.Metadata)] = &timerTriggerWithCron{
trigger: t,
cron: timer.newCron(t),
}
@@ -118,14 +111,7 @@ func (timer *Timer) syncCron(triggers []crd.TimeTrigger) error {
// process removed triggers
for k, v := range timer.triggers {
found := false
for _, t := range triggers {
if t.Metadata.Name == k {
found = true
break
}
}
if !found {
if _, found := triggerMap[k]; !found {
if v.cron != nil {
v.cron.Stop()
log.Printf("Cron for time trigger %s stopped", v.trigger.Metadata.Name)
@@ -143,7 +129,11 @@ func (timer *Timer) newCron(t crd.TimeTrigger) *cron.Cron {
headers := map[string]string{
"X-Fission-Timer-Name": t.Metadata.Name,
}
(*timer.publisher).Publish("", headers, fission.UrlForFunction(t.Spec.FunctionReference.Name))
// with the addition of multi-tenancy, the users can create functions in any namespace. however,
// the triggers can only be created in the same namespace as the function.
// so essentially, function namespace = trigger namespace.
(*timer.publisher).Publish("", headers, fission.UrlForFunction(t.Spec.FunctionReference.Name, t.Metadata.Namespace))
})
c.Start()
log.Printf("Add new cron for time trigger %v", t.Metadata.Name)
+13
View File
@@ -435,3 +435,16 @@ var errorDescriptions = []string{
const (
ArchiveLiteralSizeLimit int64 = 256 * 1024
)
const (
FissionBuilderSA = "fission-builder"
FissionFetcherSA = "fission-fetcher"
SecretConfigMapGetterCR = "secret-configmap-getter"
SecretConfigMapGetterRB = "secret-configmap-getter-binding"
PackageGetterCR = "package-getter"
PackageGetterRB = "package-getter-binding"
ClusterRole = "ClusterRole"
)