Repository: fission/fission (go)

Issue:
The cold start failure of poolmgr function is not counted in metric.
<!-- Please answer these questions before submitting your issue. Thanks! -->

<!-- Documentation URL: https://fission.io/docs -->
<!-- Troubleshooting guide: https://fission.io/docs/trouble-shooting/ -->

**Fission/Kubernetes version**

<!-- If you tested with other services, for example Istio, please also provide the version of service as well. -->

<pre>
$ fission version
$ kubectl version
</pre>

**Kubernetes platform (e.g. Google Kubernetes Engine)**

**Describe the bug**
<!--A clear and concise description of what the bug is.-->
The cold start failure of poolmgr function is not counted in metric.

**To Reproduce**

<!-- Please provide steps for reproducing the error. -->
ColdStarts
<img width="1225" alt="image" src="https://user-images.githubusercontent.com/17308208/228413803-cec43dd5-1621-4d69-a0d6-b4ae2dcc0b8a.png">
ColdStartsErrors
<img width="895" alt="image" src="https://user-images.githubusercontent.com/17308208/228413777-d9d6e5dd-baf7-4271-918c-5dadd31197f4.png">


**Expected result**
<!--A clear and concise description of what you expected to happen.-->
count the cold start error for the poolmgr function

**Actual result**
not count

**Screenshots/Dump file**
<!--If applicable, add screenshots/fission dump file to help explain your problem.-->

<pre>
$ fission support dump
</pre>

**Additional context**
<!--Add any other context about the problem here.-->


Pull request description:
fix: lose cold start error counter for the poolmgr functions
<!--  Thanks for sending a pull request! We request you provide detailed description as much as possible. -->

## Description
<!--- Describe your changes in detail. -->
<!-- Typically try to give details of what, why and how of the PR changes. -->
The cold start failure of poolmgr function is not counted in metric.

## Which issue(s) this PR fixes:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes #2754

## Testing
<!--- Please describe in detail how you tested your changes. -->

## Checklist:
<!-- Please tick following checkboxes as per your understanding. -->
- [x] I ran tests as well as code linting locally to verify my changes. 
- [ ] I have done manual verification of my changes, changes working as expected.
- [ ] I have added new tests to cover my changes.
- [x] My changes follow contributing guidelines of Fission.
- [x] I have signed all of my commits.


Patch:
diff --git a/charts/fission-all/Chart.yaml b/charts/fission-all/Chart.yaml
index 7e3870b2..228f7710 100644
--- a/charts/fission-all/Chart.yaml
+++ b/charts/fission-all/Chart.yaml
@@ -1,7 +1,7 @@
 apiVersion: v2
 name: fission-all
-version: v1.18.0
-appVersion: v1.18.0
+version: v1.19.0-rc1
+appVersion: v1.19.0-rc1
 description: Fission is a fast serverless framework for Kubernetes.
 home: https://fission.io/
 icon: https://fission.io/images/fission-logo-white.svg
diff --git a/charts/fission-all/values.yaml b/charts/fission-all/values.yaml
index 5c99ea0d..0c28ba5d 100644
--- a/charts/fission-all/values.yaml
+++ b/charts/fission-all/values.yaml
@@ -25,7 +25,7 @@ image: fission/fission-bundle
 ## It is also used by the chart to identify version of the few more images apart from fission-bundle.
 ## Keep it empty for using latest tag.
 ##
-imageTag: v1.18.0
+imageTag: v1.19.0-rc1
 
 ## pullPolicy represents the pull policy to use for images in the chart.
 ##
@@ -103,7 +103,7 @@ fetcher:
   ## image represents the image of the fetcher component.
   image: fission/fetcher
   ## imageTag represents the tag of the image of the fetcher component.
-  imageTag: v1.18.0
+  imageTag: v1.19.0-rc1
 
   ## Fetcher is only for to downloading or uploading archive.
   ## Normally, you don't need to change the value here, unless necessary.
@@ -685,7 +685,7 @@ preUpgradeChecks:
   image: fission/pre-upgrade-checks
   ## pre-install/pre-upgrade checks image version
   ##
-  imageTag: v1.18.0
+  imageTag: v1.19.0-rc1
 
 ## Fission post-install/post-upgrade reporting live in this image
 ##
diff --git a/pkg/builder/client/client.go b/pkg/builder/client/client.go
index 6cddf94a..f77ec7a2 100644
--- a/pkg/builder/client/client.go
+++ b/pkg/builder/client/client.go
@@ -62,7 +62,7 @@ func (c *Client) Build(ctx context.Context, req *builder.PackageBuildRequest) (*
 
 	resp, err := ctxhttp.Post(ctx, c.httpClient.StandardClient(), c.url, "application/json", bytes.NewReader(body))
 	if err != nil {
-		return nil, ferror.MakeErrorFromHTTP(resp)
+		return nil, err
 	}
 	defer resp.Body.Close()
 
diff --git a/pkg/executor/executortype/container/containermgr.go b/pkg/executor/executortype/container/containermgr.go
index 60e5a7c1..436029e6 100644
--- a/pkg/executor/executortype/container/containermgr.go
+++ b/pkg/executor/executortype/container/containermgr.go
@@ -457,7 +457,7 @@ func (caaf *Container) fnCreate(ctx context.Context, fn *fv1.Function) (*fscache
 	_, err = caaf.fsCache.Add(*fsvc)
 	if err != nil {
 		caaf.logger.Error("error adding function to cache", zap.Error(err), zap.Any("function", fsvc.Function))
-		metrics.FuncError.WithLabelValues(fn.ObjectMeta.Name, fn.ObjectMeta.Namespace).Inc()
+		metrics.ColdStartsError.WithLabelValues(fn.ObjectMeta.Name, fn.ObjectMeta.Namespace).Inc()
 		return fsvc, err
 	}
 
diff --git a/pkg/executor/executortype/newdeploy/newdeploymgr.go b/pkg/executor/executortype/newdeploy/newdeploymgr.go
index da8e5c88..627f24e3 100644
--- a/pkg/executor/executortype/newdeploy/newdeploymgr.go
+++ b/pkg/executor/executortype/newdeploy/newdeploymgr.go
@@ -500,7 +500,7 @@ func (deploy *NewDeploy) fnCreate(ctx context.Context, fn *fv1.Function) (*fscac
 	_, err = deploy.fsCache.Add(*fsvc)
 	if err != nil {
 		deploy.logger.Error("error adding function to cache", zap.Error(err), zap.Any("function", fsvc.Function))
-		metrics.FuncError.WithLabelValues(fn.ObjectMeta.Name, fn.ObjectMeta.Namespace).Inc()
+		metrics.ColdStartsError.WithLabelValues(fn.ObjectMeta.Name, fn.ObjectMeta.Namespace).Inc()
 		return fsvc, err
 	}
 
diff --git a/pkg/executor/executortype/poolmgr/gp.go b/pkg/executor/executortype/poolmgr/gp.go
index bf4184c9..40eda15e 100644
--- a/pkg/executor/executortype/poolmgr/gp.go
+++ b/pkg/executor/executortype/poolmgr/gp.go
@@ -47,7 +47,6 @@ import (
 	fv1 "github.com/fission/fission/pkg/apis/core/v1"
 	"github.com/fission/fission/pkg/crd"
 	"github.com/fission/fission/pkg/executor/fscache"
-	"github.com/fission/fission/pkg/executor/metrics"
 	fetcherClient "github.com/fission/fission/pkg/fetcher/client"
 	fetcherConfig "github.com/fission/fission/pkg/fetcher/config"
 	"github.com/fission/fission/pkg/generated/clientset/versioned"
@@ -517,7 +516,7 @@ func (gp *GenericPool) getFuncSvc(ctx context.Context, fn *fv1.Function) (*fscac
 		// Remove old versions function pods
 		for _, pod := range podList.Items {
 			// Delete pod no matter what status it is
-			gp.kubernetesClient.CoreV1().Pods(gp.fnNamespace).Delete(ctx, pod.ObjectMeta.Name, metav1.DeleteOptions{}) //nolint errcheck
+			gp.kubernetesClient.CoreV1().Pods(gp.fnNamespace).Delete(ctx, pod.ObjectMeta.Name, metav1.DeleteOptions{}) // nolint errcheck
 		}
 	}
 
@@ -613,7 +612,6 @@ func (gp *GenericPool) getFuncSvc(ctx context.Context, fn *fv1.Function) (*fscac
 	gp.fsCache.PodToFsvc.Store(pod.GetObjectMeta().GetName(), fsvc)
 	gp.podFSVCMap.Store(pod.ObjectMeta.Name, []interface{}{crd.CacheKey(fsvc.Function), fsvc.Address})
 	gp.fsCache.AddFunc(ctx, *fsvc, fn.GetRequestPerPod())
-	metrics.ColdStarts.WithLabelValues(fn.ObjectMeta.Name, fn.ObjectMeta.Namespace).Inc()
 
 	logger.Info("added function service",
 		zap.String("pod", pod.ObjectMeta.Name),
diff --git a/pkg/executor/executortype/poolmgr/gpm.go b/pkg/executor/executortype/poolmgr/gpm.go
index 9ea0b181..f93cc4f7 100644
--- a/pkg/executor/executortype/poolmgr/gpm.go
+++ b/pkg/executor/executortype/poolmgr/gpm.go
@@ -26,6 +26,7 @@ import (
 	"sync"
 	"time"
 
+	"github.com/fission/fission/pkg/executor/metrics"
 	"github.com/hashicorp/go-multierror"
 	"go.opentelemetry.io/otel/attribute"
 	"go.uber.org/zap"
@@ -186,7 +187,16 @@ func (gpm *GenericPoolManager) GetTypeName(ctx context.Context) fv1.ExecutorType
 	return fv1.ExecutorTypePoolmgr
 }
 
-func (gpm *GenericPoolManager) GetFuncSvc(ctx context.Context, fn *fv1.Function) (*fscache.FuncSvc, error) {
+func (gpm *GenericPoolManager) GetFuncSvc(ctx context.Context, fn *fv1.Function) (fnSvc *fscache.FuncSvc, fErr error) {
+	defer func() {
+		if fErr != nil {
+			metrics.ColdStartsError.WithLabelValues(fn.ObjectMeta.Name, fn.ObjectMeta.Namespace).Inc()
+			return
+		}
+
+		metrics.ColdStarts.WithLabelValues(fn.ObjectMeta.Name, fn.ObjectMeta.Namespace).Inc()
+	}()
+
 	otelUtils.SpanTrackEvent(ctx, "GetFuncSvc", otelUtils.GetAttributesForFunction(fn)...)
 	logger := otelUtils.LoggerWithTraceID(ctx, gpm.logger)
 
@@ -194,12 +204,14 @@ func (gpm *GenericPoolManager) GetFuncSvc(ctx context.Context, fn *fv1.Function)
 	logger.Debug("getting environment for function", zap.String("function", fn.ObjectMeta.Name))
 	env, err := gpm.getFunctionEnv(ctx, fn)
 	if err != nil {
-		return nil, err
+		fErr = err
+		return
 	}
 
 	pool, created, err := gpm.getPool(ctx, env)
 	if err != nil {
-		return nil, err
+		fErr = err
+		return
 	}
 
 	if created {
@@ -209,7 +221,8 @@ func (gpm *GenericPoolManager) GetFuncSvc(ctx context.Context, fn *fv1.Function)
 	// from GenericPool -> get one function container
 	// (this also adds to the cache)
 	logger.Debug("getting function service from pool", zap.String("function", fn.ObjectMeta.Name))
-	return pool.getFuncSvc(ctx, fn)
+	fnSvc, fErr = pool.getFuncSvc(ctx, fn)
+	return fnSvc, fErr
 }
 
 func (gpm *GenericPoolManager) GetFuncSvcFromCache(ctx context.Context, fn *fv1.Function) (*fscache.FuncSvc, error) {
diff --git a/pkg/executor/metrics/metrics.go b/pkg/executor/metrics/metrics.go
index 6a149906..6bca3aad 100644
--- a/pkg/executor/metrics/metrics.go
+++ b/pkg/executor/metrics/metrics.go
@@ -41,7 +41,7 @@ var (
 		},
 		functionLabels,
 	)
-	FuncError = prometheus.NewCounterVec(
+	ColdStartsError = prometheus.NewCounterVec(
 		prometheus.CounterOpts{
 			Name: "fission_function_cold_start_errors_total",
 			Help: "Count of fission cold start errors",
@@ -54,5 +54,5 @@ func init() {
 	registry := metrics.Registry
 	registry.MustRegister(ColdStarts)
 	registry.MustRegister(FuncRunningSummary)
-	registry.MustRegister(FuncError)
+	registry.MustRegister(ColdStartsError)
 }
diff --git a/pkg/fission-cli/cmd/environment/create.go b/pkg/fission-cli/cmd/environment/create.go
index e0e38176..4d99e4d0 100644
--- a/pkg/fission-cli/cmd/environment/create.go
+++ b/pkg/fission-cli/cmd/environment/create.go
@@ -100,7 +100,7 @@ func (opts *CreateSubCommand) run(input cli.Input) (err error) {
 		}
 
 		specFile := fmt.Sprintf("env-%v.yaml", m.Name)
-		err = spec.SpecSave(*opts.env, specFile)
+		err = spec.SpecSave(*opts.env, specFile, false)
 		if err != nil {
 			return errors.Wrap(err, "error saving environment spec")
 		}
diff --git a/pkg/fission-cli/cmd/environment/update.go b/pkg/fission-cli/cmd/environment/update.go
index ea89791a..5c1bbb6c 100644
--- a/pkg/fission-cli/cmd/environment/update.go
+++ b/pkg/fission-cli/cmd/environment/update.go
@@ -29,6 +29,7 @@ import (
 	fv1 "github.com/fission/fission/pkg/apis/core/v1"
 	"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
 	"github.com/fission/fission/pkg/fission-cli/cmd"
+	"github.com/fission/fission/pkg/fission-cli/cmd/spec"
 	"github.com/fission/fission/pkg/fission-cli/console"
 	flagkey "github.com/fission/fission/pkg/fission-cli/flag/key"
 	"github.com/fission/fission/pkg/fission-cli/util"
@@ -78,7 +79,20 @@ func (opts *UpdateSubCommand) complete(input cli.Input) (err error) {
 }
 
 func (opts *UpdateSubCommand) run(input cli.Input) error {
+	m := opts.env.ObjectMeta
+	if input.Bool(flagkey.SpecSave) {
+		err := opts.env.Validate()
+		if err != nil {
+			return fv1.AggregateValidationErrors("Environment", err)
+		}
 
+		specFile := fmt.Sprintf("env-%s.yaml", m.Name)
+		err = spec.SpecSave(*opts.env, specFile, true)
+		if err != nil {
+			return errors.Wrap(err, "error saving environment spec")
+		}
+		return nil
+	}
 	enew, err := opts.Client().FissionClientSet.CoreV1().Environments(opts.env.ObjectMeta.Namespace).Update(input.Context(), opts.env, metav1.UpdateOptions{})
 	if err != nil {
 		return errors.Wrap(err, "error updating environment")
diff --git a/pkg/fission-cli/cmd/function/create.go b/pkg/fission-cli/cmd/function/create.go
index 0b6e3e5d..0c98471d 100644
--- a/pkg/fission-cli/cmd/function/create.go
+++ b/pkg/fission-cli/cmd/function/create.go
@@ -363,7 +363,7 @@ func (opts *CreateSubCommand) run(input cli.Input) error {
 	}
 
 	if input.Bool(flagkey.SpecSave) {
-		err := spec.SpecSave(*opts.function, opts.specFile)
+		err := spec.SpecSave(*opts.function, opts.specFile, false)
 		if err != nil {
 			return errors.Wrap(err, "error saving function spec")
 		}
diff --git a/pkg/fission-cli/cmd/function/run_container.go b/pkg/fission-cli/cmd/function/run_container.go
index 7b46dc63..60b02d5c 100644
--- a/pkg/fission-cli/cmd/function/run_container.go
+++ b/pkg/fission-cli/cmd/function/run_container.go
@@ -226,7 +226,7 @@ func (opts *RunContainerSubCommand) run(input cli.Input) error {
 	}
 
 	if input.Bool(flagkey.SpecSave) {
-		err := spec.SpecSave(*opts.function, opts.specFile)
+		err := spec.SpecSave(*opts.function, opts.specFile, false)
 		if err != nil {
 			return errors.Wrap(err, "error saving function spec")
 		}
diff --git a/pkg/fission-cli/cmd/function/update.go b/pkg/fission-cli/cmd/function/update.go
index e39aefce..14704db0 100644
--- a/pkg/fission-cli/cmd/function/update.go
+++ b/pkg/fission-cli/cmd/function/update.go
@@ -27,6 +27,7 @@ import (
 	"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
 	"github.com/fission/fission/pkg/fission-cli/cmd"
 	_package "github.com/fission/fission/pkg/fission-cli/cmd/package"
+	"github.com/fission/fission/pkg/fission-cli/cmd/spec"
 	"github.com/fission/fission/pkg/fission-cli/console"
 	flagkey "github.com/fission/fission/pkg/fission-cli/flag/key"
 	"github.com/fission/fission/pkg/fission-cli/util"
@@ -35,6 +36,7 @@ import (
 type UpdateSubCommand struct {
 	cmd.CommandActioner
 	function *fv1.Function
+	specFile string
 }
 
 func Update(input cli.Input) error {
@@ -55,6 +57,9 @@ func (opts *UpdateSubCommand) complete(input cli.Input) error {
 	if err != nil {
 		return errors.Wrap(err, "error in updating function ")
 	}
+	if input.Bool(flagkey.SpecSave) {
+		opts.specFile = fmt.Sprintf("function-%s.yaml", fnName)
+	}
 
 	function, err := opts.Client().FissionClientSet.CoreV1().Functions(fnNamespace).Get(input.Context(), input.String(flagkey.FnName), metav1.GetOptions{})
 	if err != nil {
@@ -193,7 +198,7 @@ func (opts *UpdateSubCommand) complete(input cli.Input) error {
 		return errors.Errorf("Package is used by multiple functions, use --%v to force update", flagkey.PkgForce)
 	}
 
-	newPkgMeta, err := _package.UpdatePackage(input, opts.Client(), pkg)
+	newPkgMeta, err := _package.UpdatePackage(input, opts.Client(), opts.specFile, pkg)
 	if err != nil {
 		return errors.Wrap(err, fmt.Sprintf("error updating package '%v'", pkgName))
 	}
@@ -243,6 +248,17 @@ func (opts *UpdateSubCommand) complete(input cli.Input) error {
 }
 
 func (opts *UpdateSubCommand) run(input cli.Input) error {
+	if input.Bool(flagkey.SpecSave) {
+		err := opts.function.Validate()
+		if err != nil {
+			return fv1.AggregateValidationErrors("Function", err)
+		}
+		err = spec.SpecSave(*opts.function, opts.specFile, false)
+		if err != nil {
+			return errors.Wrap(err, "error saving function spec")
+		}
+		return nil
+	}
 	_, err := opts.Client().FissionClientSet.CoreV1().Functions(opts.function.Namespace).Update(input.Context(), opts.function, metav1.UpdateOptions{})
 	if err != nil {
 		return errors.Wrap(err, "error updating function")
diff --git a/pkg/fission-cli/cmd/httptrigger/create.go b/pkg/fission-cli/cmd/httptrigger/create.go
index 235d8899..dac0e48f 100644
--- a/pkg/fission-cli/cmd/httptrigger/create.go
+++ b/pkg/fission-cli/cmd/httptrigger/create.go
@@ -219,7 +219,7 @@ func (opts *CreateSubCommand) run(input cli.Input) error {
 
 	if input.Bool(flagkey.SpecSave) {
 		specFile := fmt.Sprintf("route-%v.yaml", opts.trigger.ObjectMeta.Name)
-		err := spec.SpecSave(*opts.trigger, specFile)
+		err := spec.SpecSave(*opts.trigger, specFile, false)
 		if err != nil {
 			return errors.Wrap(err, "error saving HTTP trigger spec")
 		}
diff --git a/pkg/fission-cli/cmd/httptrigger/update.go b/pkg/fission-cli/cmd/httptrigger/update.go
index b1357c79..275de994 100644
--- a/pkg/fission-cli/cmd/httptrigger/update.go
+++ b/pkg/fission-cli/cmd/httptrigger/update.go
@@ -26,6 +26,7 @@ import (
 	fv1 "github.com/fission/fission/pkg/apis/core/v1"
 	"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
 	"github.com/fission/fission/pkg/fission-cli/cmd"
+	"github.com/fission/fission/pkg/fission-cli/cmd/spec"
 	"github.com/fission/fission/pkg/fission-cli/console"
 	flagkey "github.com/fission/fission/pkg/fission-cli/flag/key"
 	"github.com/fission/fission/pkg/fission-cli/util"
@@ -148,7 +149,18 @@ func (opts *UpdateSubCommand) complete(input cli.Input) (err error) {
 }
 
 func (opts *UpdateSubCommand) run(input cli.Input) error {
-
+	if input.Bool(flagkey.SpecSave) {
+		err := opts.trigger.Validate()
+		if err != nil {
+			return fv1.AggregateValidationErrors("HTTPTrigger", err)
+		}
+		specFile := fmt.Sprintf("route-%s.yaml", opts.trigger.ObjectMeta.Name)
+		err = spec.SpecSave(*opts.trigger, specFile, true)
+		if err != nil {
+			return errors.Wrap(err, "error saving HTTP trigger spec")
+		}
+		return nil
+	}
 	err := util.CheckHTTPTriggerDuplicates(input.Context(), opts.Client(), opts.trigger)
 	if err != nil {
 		return errors.Wrap(err, "Error while creating HTTP Trigger")
diff --git a/pkg/fission-cli/cmd/kubewatch/create.go b/pkg/fission-cli/cmd/kubewatch/create.go
index 0ba2c66a..18e7e3d4 100644
--- a/pkg/fission-cli/cmd/kubewatch/create.go
+++ b/pkg/fission-cli/cmd/kubewatch/create.go
@@ -119,7 +119,7 @@ func (opts *CreateSubCommand) run(input cli.Input) error {
 
 	if input.Bool(flagkey.SpecSave) {
 		specFile := fmt.Sprintf("kubewatch-%v.yaml", opts.watcher.ObjectMeta.Name)
-		err := spec.SpecSave(*opts.watcher, specFile)
+		err := spec.SpecSave(*opts.watcher, specFile, false)
 		if err != nil {
 			return errors.Wrap(err, "error saving kubewatch spec")
 		}
diff --git a/pkg/fission-cli/cmd/mqtrigger/create.go b/pkg/fission-cli/cmd/mqtrigger/create.go
index 893fe870..b799deab 100644
--- a/pkg/fission-cli/cmd/mqtrigger/create.go
+++ b/pkg/fission-cli/cmd/mqtrigger/create.go
@@ -203,7 +203,7 @@ func (opts *CreateSubCommand) run(input cli.Input) error {
 
 	if input.Bool(flagkey.SpecSave) {
 		specFile := fmt.Sprintf("mqtrigger-%v.yaml", opts.trigger.ObjectMeta.Name)
-		err := spec.SpecSave(*opts.trigger, specFile)
+		err := spec.SpecSave(*opts.trigger, specFile, false)
 		if err != nil {
 			return errors.Wrap(err, "error saving message queue trigger spec")
 		}
diff --git a/pkg/fission-cli/cmd/mqtrigger/update.go b/pkg/fission-cli/cmd/mqtrigger/update.go
index 7c716041..14dbe8e8 100644
--- a/pkg/fission-cli/cmd/mqtrigger/update.go
+++ b/pkg/fission-cli/cmd/mqtrigger/update.go
@@ -25,6 +25,7 @@ import (
 	fv1 "github.com/fission/fission/pkg/apis/core/v1"
 	"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
 	"github.com/fission/fission/pkg/fission-cli/cmd"
+	"github.com/fission/fission/pkg/fission-cli/cmd/spec"
 	"github.com/fission/fission/pkg/fission-cli/console"
 	flagkey "github.com/fission/fission/pkg/fission-cli/flag/key"
 	"github.com/fission/fission/pkg/fission-cli/util"
@@ -148,6 +149,18 @@ func (opts *UpdateSubCommand) complete(input cli.Input) (err error) {
 }
 
 func (opts *UpdateSubCommand) run(input cli.Input) error {
+	if input.Bool(flagkey.SpecSave) {
+		err := opts.trigger.Validate()
+		if err != nil {
+			return fv1.AggregateValidationErrors("MessageQueueTrigger", err)
+		}
+		specFile := fmt.Sprintf("mqtrigger-%s.yaml", opts.trigger.ObjectMeta.Name)
+		err = spec.SpecSave(*opts.trigger, specFile, true)
+		if err != nil {
+			return errors.Wrap(err, "error saving message queue trigger spec")
+		}
+		return nil
+	}
 	_, err := opts.Client().FissionClientSet.CoreV1().MessageQueueTriggers(opts.trigger.ObjectMeta.Namespace).Update(input.Context(), opts.trigger, metav1.UpdateOptions{})
 	if err != nil {
 		return errors.Wrap(err, "error updating message queue trigger")
diff --git a/pkg/fission-cli/cmd/package/create.go b/pkg/fission-cli/cmd/package/create.go
index 23923a32..ffcde178 100644
--- a/pkg/fission-cli/cmd/package/create.go
+++ b/pkg/fission-cli/cmd/package/create.go
@@ -216,7 +216,7 @@ func CreatePackage(input cli.Input, client cmd.Client, pkgName string, pkgNamesp
 			return &pkg.ObjectMeta, nil
 		}
 
-		err = spec.SpecSave(*pkg, specFile)
+		err = spec.SpecSave(*pkg, specFile, false)
 		if err != nil {
 			return nil, errors.Wrap(err, "error saving package spec")
 		}
diff --git a/pkg/fission-cli/cmd/package/package.go b/pkg/fission-cli/cmd/package/package.go
index 5f1d7101..1e238b1a 100644
--- a/pkg/fission-cli/cmd/package/package.go
+++ b/pkg/fission-cli/cmd/package/package.go
@@ -174,7 +174,7 @@ func CreateArchive(client cmd.Client, input cli.Input, includeFiles []string, no
 				aus.Name = oldAus.Name
 			} else {
 				// save the uploadspec
-				err := spec.SpecSave(*aus, specFile)
+				err := spec.SpecSave(*aus, specFile, false)
 				if err != nil {
 					return nil, errors.Wrap(err, "error saving archive spec")
 				}
diff --git a/pkg/fission-cli/cmd/package/update.go b/pkg/fission-cli/cmd/package/update.go
index faed8dae..e10ce88e 100644
--- a/pkg/fission-cli/cmd/package/update.go
+++ b/pkg/fission-cli/cmd/package/update.go
@@ -28,7 +28,9 @@ import (
 	fv1 "github.com/fission/fission/pkg/apis/core/v1"
 	"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
 	"github.com/fission/fission/pkg/fission-cli/cmd"
+	"github.com/fission/fission/pkg/fission-cli/cmd/spec"
 	flagkey "github.com/fission/fission/pkg/fission-cli/flag/key"
+	"github.com/fission/fission/pkg/fission-cli/util"
 )
 
 type UpdateSubCommand struct {
@@ -61,6 +63,7 @@ func (opts *UpdateSubCommand) complete(input cli.Input) (err error) {
 }
 
 func (opts *UpdateSubCommand) run(input cli.Input) error {
+	pkgName := input.String(flagkey.PkgName)
 	pkg, err := opts.Client().FissionClientSet.CoreV1().Packages(opts.pkgNamespace).Get(input.Context(), opts.pkgName, metav1.GetOptions{})
 	if err != nil {
 		return err
@@ -79,8 +82,8 @@ func (opts *UpdateSubCommand) run(input cli.Input) error {
 	if !forceUpdate && len(fnList) > 1 {
 		return errors.Errorf("package is used by multiple functions, use --%v to force update", flagkey.PkgForce)
 	}
-
-	newPkgMeta, err := UpdatePackage(input, opts.Client(), pkg)
+	specFile := fmt.Sprintf("package-%s.yaml", pkgName)
+	newPkgMeta, err := UpdatePackage(input, opts.Client(), specFile, pkg)
 	if err != nil {
 		return errors.Wrap(err, "error updating package")
 	}
@@ -95,7 +98,7 @@ func (opts *UpdateSubCommand) run(input cli.Input) error {
 	return nil
 }
 
-func UpdatePackage(input cli.Input, client cmd.Client, pkg *fv1.Package) (*metav1.ObjectMeta, error) {
+func UpdatePackage(input cli.Input, client cmd.Client, specFile string, pkg *fv1.Package) (*metav1.ObjectMeta, error) {
 	envName := input.String(flagkey.PkgEnvironment)
 	srcArchiveFiles := input.StringSlice(flagkey.PkgSrcArchive)
 	deployArchiveFiles := input.StringSlice(flagkey.PkgDeployArchive)
@@ -174,6 +177,27 @@ func UpdatePackage(input cli.Input, client cmd.Client, pkg *fv1.Package) (*metav
 		}
 	}
 
+	if input.Bool(flagkey.SpecSave) {
+		// if a package with the same spec exists, don't create a new spec file
+		fr, err := spec.ReadSpecs(util.GetSpecDir(input), util.GetSpecIgnore(input), false)
+		if err != nil {
+			return nil, errors.Wrap(err, "error reading specs")
+		}
+
+		obj := fr.SpecExists(pkg, true, true)
+		if obj != nil {
+			pkg := obj.(*fv1.Package)
+			fmt.Printf("Re-using previously created package %s\n", pkg.ObjectMeta.Name)
+			return &pkg.ObjectMeta, nil
+		}
+
+		err = spec.SpecSave(*pkg, specFile, true)
+		if err != nil {
+			return nil, errors.Wrap(err, "error saving package spec")
+		}
+		return &pkg.ObjectMeta, nil
+	}
+
 	newPkgMeta, err := client.FissionClientSet.CoreV1().Packages(pkg.ObjectMeta.Namespace).Update(input.Context(), pkg, metav1.UpdateOptions{})
 	if err != nil {
 		return nil, errors.Wrap(err, "update package")
diff --git a/pkg/fission-cli/cmd/spec/spec.go b/pkg/fission-cli/cmd/spec/spec.go
index e3fe0766..cfe83c13 100644
--- a/pkg/fission-cli/cmd/spec/spec.go
+++ b/pkg/fission-cli/cmd/spec/spec.go
@@ -127,7 +127,7 @@ func MapKey(m *metav1.ObjectMeta) string {
 }
 
 // save saves object encoded value to spec file under given spec directory
-func save(data []byte, specDir string, specFile string) error {
+func save(data []byte, specDir string, specFile string, truncate bool) error {
 	// verify
 	if _, err := os.Stat(filepath.Join(specDir, "fission-deployment-config.yaml")); os.IsNotExist(err) {
 		return errors.Wrap(err, "Couldn't find specs, run `fission spec init` first")
@@ -137,6 +137,9 @@ func save(data []byte, specDir string, specFile string) error {
 	// check if the file is new
 	newFile := false
 	if _, err := os.Stat(filename); os.IsNotExist(err) {
+		if truncate {
+			return errors.Errorf("spec file does not exists")
+		}
 		newFile = true
 	}
 
@@ -147,11 +150,19 @@ func save(data []byte, specDir string, specFile string) error {
 	}
 	defer f.Close()
 
-	// if we're appending, add a yaml document separator
-	if !newFile {
-		_, err = f.Write([]byte("\n---\n"))
+	if truncate {
+		err = f.Truncate(0)
 		if err != nil {
-			return errors.Wrap(err, "couldn't write to spec file")
+			return errors.Wrap(err, "couldn't truncate the spec file")
+		}
+
+	} else {
+		// if we're appending, add a yaml document separator
+		if !newFile {
+			_, err = f.Write([]byte("\n---\n"))
+			if err != nil {
+				return errors.Wrap(err, "couldn't write to spec file")
+			}
 		}
 	}
 
@@ -164,7 +175,7 @@ func save(data []byte, specDir string, specFile string) error {
 }
 
 // called from `fission * create --spec`
-func SpecSave(resource interface{}, specFile string) error {
+func SpecSave(resource interface{}, specFile string, update bool) error {
 	var specDir = "specs"
 
 	meta, kind, data, err := crdToYaml(resource)
@@ -186,7 +197,11 @@ func SpecSave(resource interface{}, specFile string) error {
 		return errors.Errorf("same name resource (%v) already exists in namespace (%v)", meta.Name, meta.Namespace)
 	}
 
-	err = save(data, specDir, specFile)
+	truncate := false
+	if update {
+		truncate = true
+	}
+	err = save(data, specDir, specFile, truncate)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/fission-cli/cmd/timetrigger/create.go b/pkg/fission-cli/cmd/timetrigger/create.go
index 22c45c6a..fd3dbefc 100644
--- a/pkg/fission-cli/cmd/timetrigger/create.go
+++ b/pkg/fission-cli/cmd/timetrigger/create.go
@@ -135,7 +135,7 @@ func (opts *CreateSubCommand) run(input cli.Input) error {
 
 	if input.Bool(flagkey.SpecSave) {
 		specFile := fmt.Sprintf("timetrigger-%v.yaml", opts.trigger.ObjectMeta.Name)
-		err := spec.SpecSave(*opts.trigger, specFile)
+		err := spec.SpecSave(*opts.trigger, specFile, false)
 		if err != nil {
 			return errors.Wrap(err, "error saving time trigger spec")
 		}
diff --git a/pkg/fission-cli/cmd/timetrigger/update.go b/pkg/fission-cli/cmd/timetrigger/update.go
index d2f0832d..ab9300cf 100644
--- a/pkg/fission-cli/cmd/timetrigger/update.go
+++ b/pkg/fission-cli/cmd/timetrigger/update.go
@@ -25,6 +25,7 @@ import (
 	fv1 "github.com/fission/fission/pkg/apis/core/v1"
 	"github.com/fission/fission/pkg/fission-cli/cliwrapper/cli"
 	"github.com/fission/fission/pkg/fission-cli/cmd"
+	"github.com/fission/fission/pkg/fission-cli/cmd/spec"
 	"github.com/fission/fission/pkg/fission-cli/console"
 	flagkey "github.com/fission/fission/pkg/fission-cli/flag/key"
 	"github.com/fission/fission/pkg/fission-cli/util"
@@ -86,6 +87,18 @@ func (opts *UpdateSubCommand) complete(input cli.Input) error {
 }
 
 func (opts *UpdateSubCommand) run(input cli.Input) error {
+	if input.Bool(flagkey.SpecSave) {
+		err := opts.trigger.Validate()
+		if err != nil {
+			return fv1.AggregateValidationErrors("TimeTrigger", err)
+		}
+		specFile := fmt.Sprintf("timetrigger-%s.yaml", opts.trigger.ObjectMeta.Name)
+		err = spec.SpecSave(*opts.trigger, specFile, true)
+		if err != nil {
+			return errors.Wrap(err, "error saving time trigger spec")
+		}
+		return nil
+	}
 	_, err := opts.Client().FissionClientSet.CoreV1().TimeTriggers(opts.trigger.ObjectMeta.Namespace).Update(input.Context(), opts.trigger, metav1.UpdateOptions{})
 	if err != nil {
 		return errors.Wrap(err, "error updating Time trigger")
diff --git a/pkg/utils/metrics/http_metrics.go b/pkg/utils/metrics/http_metrics.go
index 02549750..c1f393f0 100644
--- a/pkg/utils/metrics/http_metrics.go
+++ b/pkg/utils/metrics/http_metrics.go
@@ -67,6 +67,12 @@ func (rw *ResponseWriterWrapper) WriteHeader(statuscode int) {
 	rw.ResponseWriter.WriteHeader(statuscode)
 }
 
+func (rw *ResponseWriterWrapper) Flush() {
+	if f, ok := rw.ResponseWriter.(http.Flusher); ok {
+		f.Flush()
+	}
+}
+
 func HTTPMetricMiddleware(next http.Handler) http.Handler {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		if util.IsWebsocketRequest(r) {
diff --git a/pkg/utils/namespace.go b/pkg/utils/namespace.go
index 1e552e8d..836bdde5 100644
--- a/pkg/utils/namespace.go
+++ b/pkg/utils/namespace.go
@@ -20,7 +20,7 @@ const (
 type (
 	NamespaceResolver struct {
 		FunctionNamespace string
-		BuiderNamespace   string
+		BuilderNamespace  string
 		DefaultNamespace  string
 		FissionResourceNS map[string]string
 		Logger            *zap.Logger
@@ -40,14 +40,14 @@ var nsResolver *NamespaceResolver
 func init() {
 	nsResolver = &NamespaceResolver{
 		FunctionNamespace: os.Getenv(ENV_FUNCTION_NAMESPACE),
-		BuiderNamespace:   os.Getenv(ENV_BUILDER_NAMESPACE),
+		BuilderNamespace:  os.Getenv(ENV_BUILDER_NAMESPACE),
 		DefaultNamespace:  os.Getenv(ENV_DEFAULT_NAMESPACE),
 		FissionResourceNS: GetNamespaces(),
 		Logger:            loggerfactory.GetLogger(),
 	}
 
 	nsResolver.Logger.Debug("namespaces", zap.String("function_namespace", nsResolver.FunctionNamespace),
-		zap.String("builder_namespace", nsResolver.BuiderNamespace),
+		zap.String("builder_namespace", nsResolver.BuilderNamespace),
 		zap.String("default_namespace", nsResolver.DefaultNamespace),
 		zap.Any("fission_resource_namespace", listNamespaces(nsResolver.FissionResourceNS)))
 }
@@ -96,8 +96,8 @@ func (nsr *NamespaceResolver) FissionNSWithOptions(option ...option) map[string]
 	if options.functionNS && nsr.FunctionNamespace != "" {
 		fissionResourceNS[nsr.FunctionNamespace] = nsr.FunctionNamespace
 	}
-	if options.builderNS && nsr.BuiderNamespace != "" {
-		fissionResourceNS[nsr.BuiderNamespace] = nsr.BuiderNamespace
+	if options.builderNS && nsr.BuilderNamespace != "" {
+		fissionResourceNS[nsr.BuilderNamespace] = nsr.BuilderNamespace
 	}
 	if options.defaultNs && nsr.DefaultNamespace != "" {
 		fissionResourceNS[nsr.DefaultNamespace] = nsr.DefaultNamespace
@@ -118,7 +118,7 @@ func GetNamespaces() map[string]string {
 	if len(envValue) > 0 {
 		lstNamespaces := strings.Split(envValue, ",")
 		for _, namespace := range lstNamespaces {
-			//check to handle string with additional comma at the end of string. eg- ns1,ns2,
+			// check to handle string with additional comma at the end of string. eg- ns1,ns2,
 			if namespace != "" {
 				namespaces[namespace] = namespace
 			}
@@ -132,14 +132,14 @@ func GetNamespaces() map[string]string {
 }
 
 func (nsr *NamespaceResolver) GetBuilderNS(namespace string) string {
-	if nsr.BuiderNamespace == "" {
+	if nsr.BuilderNamespace == "" {
 		return namespace
 	}
 
 	if namespace != metav1.NamespaceDefault {
 		return namespace
 	}
-	return nsr.BuiderNamespace
+	return nsr.BuilderNamespace
 }
 
 func (nsr *NamespaceResolver) GetFunctionNS(namespace string) string {
@@ -154,7 +154,7 @@ func (nsr *NamespaceResolver) GetFunctionNS(namespace string) string {
 }
 
 func (nsr *NamespaceResolver) ResolveNamespace(namespace string) string {
-	if nsr.FunctionNamespace == "" || nsr.BuiderNamespace == "" {
+	if nsr.FunctionNamespace == "" || nsr.BuilderNamespace == "" {
 		return nsr.DefaultNamespace
 	}
 	return namespace
diff --git a/pkg/utils/profile/profile.go b/pkg/utils/profile/profile.go
index fc796c47..308e42a2 100644
--- a/pkg/utils/profile/profile.go
+++ b/pkg/utils/profile/profile.go
@@ -25,7 +25,6 @@ package profile
 
 import (
 	"context"
-	"fmt"
 	"net/http"
 	_ "net/http/pprof"
 	"os"
@@ -35,26 +34,18 @@ import (
 	"github.com/fission/fission/pkg/utils/httpserver"
 )
 
-func getPprofAddr() string {
-	pprofHost := os.Getenv("PPROF_HOST")
-	if pprofHost == "" {
-		pprofHost = "localhost"
-	}
-	pprofPort := os.Getenv("PPROF_PORT")
-	if pprofPort == "" {
-		pprofPort = "6060"
-	}
-	return fmt.Sprintf("%s:%s", pprofHost, pprofPort)
-}
-
 func ProfileIfEnabled(ctx context.Context, logger *zap.Logger) {
 	enablePprof := os.Getenv("PPROF_ENABLED")
 	if enablePprof != "true" {
 		return
 	}
+	pprofPort := os.Getenv("PPROF_PORT")
+	if pprofPort == "" {
+		pprofPort = "6060"
+	}
 
 	pprofMux := http.DefaultServeMux
 	http.DefaultServeMux = http.NewServeMux()
 
-	go httpserver.StartServer(ctx, logger, "pprof", getPprofAddr(), pprofMux)
+	go httpserver.StartServer(ctx, logger, "pprof", pprofPort, pprofMux)
 }


Test patch:
diff --git a/pkg/executor/executortype/newdeploy/newdeploymgr_test.go b/pkg/executor/executortype/newdeploy/newdeploymgr_test.go
index 3d2eea05..5cc9b288 100644
--- a/pkg/executor/executortype/newdeploy/newdeploymgr_test.go
+++ b/pkg/executor/executortype/newdeploy/newdeploymgr_test.go
@@ -66,7 +66,7 @@ func TestRefreshFuncPods(t *testing.T) {
 
 	nsResolver := utils.NamespaceResolver{
 		FunctionNamespace: functionNamespace,
-		BuiderNamespace:   builderNamespace,
+		BuilderNamespace:  builderNamespace,
 		DefaultNamespace:  defaultNamespace,
 	}
 	ndm.nsResolver = &nsResolver
diff --git a/pkg/utils/metrics/http_metrics_test.go b/pkg/utils/metrics/http_metrics_test.go
new file mode 100644
index 00000000..61ede88f
--- /dev/null
+++ b/pkg/utils/metrics/http_metrics_test.go
@@ -0,0 +1,98 @@
+package metrics
+
+import (
+	"bufio"
+	"context"
+	"fmt"
+	"io"
+	"log"
+	"net/http"
+	"net/http/httptest"
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/gorilla/mux"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+var dataRow = []byte("I'm the data Row\n")
+
+func chunkedHandler(w http.ResponseWriter, r *http.Request) {
+	ctx, cancel := context.WithCancel(r.Context())
+	ticker := time.NewTicker(time.Second) // We may set it to 10 secs
+	var wg sync.WaitGroup
+	wg.Add(1)
+	go func() {
+		defer wg.Done()
+		for {
+			select {
+			case <-ticker.C:
+				_, _ = w.Write(dataRow)
+				if f, ok := w.(http.Flusher); ok {
+					f.Flush()
+				}
+			case <-ctx.Done():
+				return
+			}
+		}
+	}()
+
+	// Emulate some work
+	time.Sleep(5 * time.Second)
+
+	// Telling the loop that keeps the connection alive to end
+	cancel()
+
+	// Waiting until the loop ends
+	wg.Wait()
+}
+
+func TestChunked(t *testing.T) {
+	mr := mux.NewRouter()
+	mr.Use(HTTPMetricMiddleware)
+	mr.Handle("/", http.HandlerFunc(chunkedHandler))
+	s := httptest.NewServer(mr)
+	defer s.Close()
+
+	resp, err := http.Get(s.URL)
+	require.NoError(t, err)
+	assert.Contains(t, resp.TransferEncoding, "chunked")
+	defer resp.Body.Close()
+
+	r := bufio.NewReader(resp.Body)
+	for {
+		line, err := readChunkedResponseLine(r)
+		if err != nil {
+			if err == io.EOF {
+				return
+			}
+			log.Fatal(err.Error())
+		}
+		if len(line) == 0 {
+			log.Println("Alive!")
+			continue
+		}
+
+		fmt.Println(string(line)) // we got the final response
+		assert.Equal(t, dataRow, append(line, '\n'))
+	}
+}
+
+func readChunkedResponseLine(r *bufio.Reader) ([]byte, error) {
+	line, isPrefix, err := r.ReadLine()
+	if err != nil {
+		return nil, err
+	}
+
+	if isPrefix {
+		rest, err := readChunkedResponseLine(r)
+		if err != nil {
+			return nil, err
+		}
+		line = append(line, rest...)
+	}
+
+	return line, nil
+}
diff --git a/pkg/utils/namespace_test.go b/pkg/utils/namespace_test.go
index 9620f2d0..ea722f86 100644
--- a/pkg/utils/namespace_test.go
+++ b/pkg/utils/namespace_test.go
@@ -226,7 +226,7 @@ func TestNamespaceResolver(t *testing.T) {
 func getFissionNamespaces(builderNS, functionNS, defaultNS string) *NamespaceResolver {
 	return &NamespaceResolver{
 		FunctionNamespace: functionNS,
-		BuiderNamespace:   builderNS,
+		BuilderNamespace:  builderNS,
 		DefaultNamespace:  defaultNS,
 	}
 }