Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add namespaces support for REST #668

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion chart/openfaas/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ oauth2Plugin:
securityContext: true

faasnetes:
image: openfaas/faas-netes:0.10.5
image: martindekov/faas-netes:0.12.0-rc1
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will revert, generally the change is in this image

readTimeout : "60s"
writeTimeout : "60s"
imagePullPolicy : "Always" # Image pull policy for deployed functions
Expand Down
7 changes: 4 additions & 3 deletions handlers/namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func MakeNamespacesLister(defaultNamespace string, clientset kubernetes.Interfac
return func(w http.ResponseWriter, r *http.Request) {
log.Println("Query namespaces")

res := list(defaultNamespace, clientset)
res := ListNamespaces(defaultNamespace, clientset)

out, _ := json.Marshal(res)
w.Header().Set("Content-Type", "application/json")
Expand Down Expand Up @@ -63,7 +63,7 @@ func NewNamespaceResolver(defaultNamespace string, kube kubernetes.Interface) Na
r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
}

allowedNamespaces := list(defaultNamespace, kube)
allowedNamespaces := ListNamespaces(defaultNamespace, kube)
ok := findNamespace(req.Namespace, allowedNamespaces)
if !ok {
return req.Namespace, fmt.Errorf("unable to manage secrets within the %s namespace", req.Namespace)
Expand All @@ -73,7 +73,8 @@ func NewNamespaceResolver(defaultNamespace string, kube kubernetes.Interface) Na
}
}

func list(defaultNamespace string, clientset kubernetes.Interface) []string {
// ListNamespaces lists all namespaces annotated with openfaas true
func ListNamespaces(defaultNamespace string, clientset kubernetes.Interface) []string {
listOptions := metav1.ListOptions{}
namespaces, err := clientset.CoreV1().Namespaces().List(context.TODO(), listOptions)

Expand Down
35 changes: 18 additions & 17 deletions handlers/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,27 @@ import (
)

// MakeSecretHandler makes a handler for Create/List/Delete/Update of
//secrets in the Kubernetes API
// secrets in the Kubernetes API
func MakeSecretHandler(defaultNamespace string, kube kubernetes.Interface) http.HandlerFunc {
handler := secretsHandler{
lookupNamespace: NewNamespaceResolver(defaultNamespace, kube),
secrets: k8s.NewSecretsClient(kube),
handler := SecretsHandler{
LookupNamespace: NewNamespaceResolver(defaultNamespace, kube),
Secrets: k8s.NewSecretsClient(kube),
}
return handler.ServeHTTP
}

type secretsHandler struct {
secrets k8s.SecretsClient
lookupNamespace NamespaceResolver
// SecretsHandler enabling to create openfaas secrets across namespaces
type SecretsHandler struct {
Secrets k8s.SecretsClient
LookupNamespace NamespaceResolver
}

func (h secretsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (h SecretsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Body != nil {
defer r.Body.Close()
}

lookupNamespace, err := h.lookupNamespace(r)
lookupNamespace, err := h.LookupNamespace(r)
if err != nil {
switch err.Error() {
case "unable to unmarshal Secret request":
Expand Down Expand Up @@ -61,8 +62,8 @@ func (h secretsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}

func (h secretsHandler) listSecrets(namespace string, w http.ResponseWriter, r *http.Request) {
res, err := h.secrets.List(namespace)
func (h SecretsHandler) listSecrets(namespace string, w http.ResponseWriter, r *http.Request) {
res, err := h.Secrets.List(namespace)
if err != nil {
status, reason := ProcessErrorReasons(err)
log.Printf("Secret list error reason: %s, %v\n", reason, err)
Expand All @@ -88,7 +89,7 @@ func (h secretsHandler) listSecrets(namespace string, w http.ResponseWriter, r *
w.Write(secretsBytes)
}

func (h secretsHandler) createSecret(namespace string, w http.ResponseWriter, r *http.Request) {
func (h SecretsHandler) createSecret(namespace string, w http.ResponseWriter, r *http.Request) {
secret := types.Secret{}
err := json.NewDecoder(r.Body).Decode(&secret)
if err != nil {
Expand All @@ -98,7 +99,7 @@ func (h secretsHandler) createSecret(namespace string, w http.ResponseWriter, r
}

secret.Namespace = namespace
err = h.secrets.Create(secret)
err = h.Secrets.Create(secret)
if err != nil {
status, reason := ProcessErrorReasons(err)
log.Printf("Secret create error reason: %s, %v\n", reason, err)
Expand All @@ -109,7 +110,7 @@ func (h secretsHandler) createSecret(namespace string, w http.ResponseWriter, r
w.WriteHeader(http.StatusAccepted)
}

func (h secretsHandler) replaceSecret(namespace string, w http.ResponseWriter, r *http.Request) {
func (h SecretsHandler) replaceSecret(namespace string, w http.ResponseWriter, r *http.Request) {
secret := types.Secret{}
err := json.NewDecoder(r.Body).Decode(&secret)
if err != nil {
Expand All @@ -119,7 +120,7 @@ func (h secretsHandler) replaceSecret(namespace string, w http.ResponseWriter, r
}

secret.Namespace = namespace
err = h.secrets.Replace(secret)
err = h.Secrets.Replace(secret)
if err != nil {
status, reason := ProcessErrorReasons(err)
log.Printf("Secret update error reason: %s, %v\n", reason, err)
Expand All @@ -130,7 +131,7 @@ func (h secretsHandler) replaceSecret(namespace string, w http.ResponseWriter, r
w.WriteHeader(http.StatusAccepted)
}

func (h secretsHandler) deleteSecret(namespace string, w http.ResponseWriter, r *http.Request) {
func (h SecretsHandler) deleteSecret(namespace string, w http.ResponseWriter, r *http.Request) {
secret := types.Secret{}
err := json.NewDecoder(r.Body).Decode(&secret)
if err != nil {
Expand All @@ -139,7 +140,7 @@ func (h secretsHandler) deleteSecret(namespace string, w http.ResponseWriter, r
return
}

err = h.secrets.Delete(namespace, secret.Name)
err = h.Secrets.Delete(namespace, secret.Name)
if err != nil {
status, reason := ProcessErrorReasons(err)
log.Printf("Secret delete error reason: %s, %v\n", reason, err)
Expand Down
1 change: 0 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,6 @@ func runOperator(kubeconfig, masterURL string) {
}

factory := controller.NewFunctionFactory(kubeClient, deployConfig)

functionNamespace := "openfaas-fn"
if namespace, exists := os.LookupEnv("function_namespace"); exists {
functionNamespace = namespace
Expand Down
7 changes: 6 additions & 1 deletion pkg/server/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
"k8s.io/klog"
)

func makeApplyHandler(namespace string, client clientset.Interface) http.HandlerFunc {
func makeApplyHandler(defaultNamespace string, client clientset.Interface) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {

if r.Body != nil {
Expand All @@ -33,6 +33,11 @@ func makeApplyHandler(namespace string, client clientset.Interface) http.Handler
}
klog.Infof("Deployment request for: %s\n", req.Service)

namespace := defaultNamespace
if len(req.Namespace) > 0 {
namespace = req.Namespace
}

opts := metav1.GetOptions{}
got, err := client.OpenfaasV1().Functions(namespace).Get(context.TODO(), req.Service, opts)
miss := false
Expand Down
17 changes: 15 additions & 2 deletions pkg/server/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,22 @@ import (
glog "k8s.io/klog"
)

func makeDeleteHandler(namespace string, client clientset.Interface) http.HandlerFunc {
func makeDeleteHandler(defaultNamespace string, client clientset.Interface) http.HandlerFunc {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am going to make a rather big suggestion that will require another refactor. I think we should have a FunctionClient interface with the needed create/update/delete/list methods. We can then create two implementation one based on the clientset and one based on the informer/lister and even one based on the CRD. This allows the logic in the handlers to be separated from the implementation. This will simplify the handlers and allow us to easily swap the implementation over time. This also means the server implementation can be unified and reused between both faas-netes and the operator because the important distinct "CRD based FunctionClient" vs "API based FunctionClient" is wrapped into the specific client implementation

Copy link
Contributor Author

@martindekov martindekov Jul 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my understanding on the proposal, you mean using only one types.FaaSHandlers instead of having almost duplicate code for the operator and faas-netes. This FunctionClient will internally switch between the details of the two (if there are any differences between them). So essentially is this some sort of a Facade pattern over the types.FaaSHandlers? I can ping you to discuss this further to gather more context on the proposed refactoring. Also this change is missing the actual creation of functions, it currently creates only the Function CRD. I was also hoping to get some insight what I should be changing in order to make this work e2e.

return func(w http.ResponseWriter, r *http.Request) {

q := r.URL.Query()
namespace := q.Get("namespace")

lookupNamespace := defaultNamespace
if len(namespace) > 0 {
lookupNamespace = namespace
}

if namespace == "kube-system" {
http.Error(w, "unable to list within the kube-system namespace", http.StatusUnauthorized)
return
}

if r.Body != nil {
defer r.Body.Close()
}
Expand All @@ -35,7 +48,7 @@ func makeDeleteHandler(namespace string, client clientset.Interface) http.Handle
return
}

err = client.OpenfaasV1().Functions(namespace).
err = client.OpenfaasV1().Functions(lookupNamespace).
Delete(context.TODO(), request.FunctionName, metav1.DeleteOptions{})
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
Expand Down
22 changes: 18 additions & 4 deletions pkg/server/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
glog "k8s.io/klog"
)

func makeListHandler(namespace string,
func makeListHandler(defaultNamespace string,
client clientset.Interface,
deploymentLister appsv1.DeploymentNamespaceLister) http.HandlerFunc {

Expand All @@ -22,10 +22,24 @@ func makeListHandler(namespace string,
defer r.Body.Close()
}

q := r.URL.Query()
namespace := q.Get("namespace")

lookupNamespace := defaultNamespace

if len(namespace) > 0 {
lookupNamespace = namespace
}

if lookupNamespace == "kube-system" {
http.Error(w, "unable to list within the kube-system namespace", http.StatusUnauthorized)
return
}

functions := []types.FunctionStatus{}

opts := metav1.ListOptions{}
res, err := client.OpenfaasV1().Functions(namespace).List(context.TODO(), opts)
res, err := client.OpenfaasV1().Functions(lookupNamespace).List(context.TODO(), opts)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
Expand All @@ -35,7 +49,7 @@ func makeListHandler(namespace string,

for _, item := range res.Items {

desiredReplicas, availableReplicas, err := getReplicas(item.Spec.Name, namespace, deploymentLister)
desiredReplicas, availableReplicas, err := getReplicas(item.Spec.Name, lookupNamespace, deploymentLister)
if err != nil {
glog.Warningf("Function listing getReplicas error: %v", err)
}
Expand All @@ -47,7 +61,7 @@ func makeListHandler(namespace string,
Image: item.Spec.Image,
Labels: item.Spec.Labels,
Annotations: item.Spec.Annotations,
Namespace: namespace,
Namespace: lookupNamespace,
}

functions = append(functions, function)
Expand Down
13 changes: 8 additions & 5 deletions pkg/server/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@ package server
import (
"encoding/json"
"net/http"

"github.com/openfaas/faas-netes/handlers"
"k8s.io/client-go/kubernetes"
)

func makeListNamespaceHandler(defaultNamespace string) func(http.ResponseWriter, *http.Request) {
func makeListNamespaceHandler(defaultNamespace string, clientset kubernetes.Interface) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
res := handlers.ListNamespaces(defaultNamespace, clientset)

defer r.Body.Close()

res, _ := json.Marshal([]string{defaultNamespace})
out, _ := json.Marshal(res)
w.Header().Set("Content-Type", "application/json")

w.WriteHeader(http.StatusOK)
w.Write(res)
w.Write(out)
}
}
17 changes: 13 additions & 4 deletions pkg/server/replicas.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,30 @@ import (
glog "k8s.io/klog"
)

func makeReplicaReader(namespace string, client clientset.Interface, lister v1.DeploymentNamespaceLister) http.HandlerFunc {
func makeReplicaReader(defaultNamespace string, client clientset.Interface, lister v1.DeploymentNamespaceLister) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
functionName := vars["name"]

q := r.URL.Query()
namespace := q.Get("namespace")

lookupNamespace := defaultNamespace

if len(namespace) > 0 {
lookupNamespace = namespace
}

opts := metav1.GetOptions{}
k8sfunc, err := client.OpenfaasV1().Functions(namespace).
k8sfunc, err := client.OpenfaasV1().Functions(lookupNamespace).
Get(context.TODO(), functionName, opts)
if err != nil {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte(err.Error()))
return
}

desiredReplicas, availableReplicas, err := getReplicas(functionName, namespace, lister)
desiredReplicas, availableReplicas, err := getReplicas(functionName, lookupNamespace, lister)
if err != nil {
glog.Warningf("Function replica reader error: %v", err)
}
Expand All @@ -42,7 +51,7 @@ func makeReplicaReader(namespace string, client clientset.Interface, lister v1.D
Name: k8sfunc.Spec.Name,
EnvProcess: k8sfunc.Spec.Handler,
Image: k8sfunc.Spec.Image,
Namespace: namespace,
Namespace: lookupNamespace,
}

res, _ := json.Marshal(result)
Expand Down
Loading