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:
@@ -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.
|
||||
@@ -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 {}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{}))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,7 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT=$(dirname $0)/../..
|
||||
|
||||
fn=nodejs-hello-$(date +%s)
|
||||
|
||||
Executable
+275
@@ -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
|
||||
@@ -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,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
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user