faas/gateway/handlers/update_handler.go
John McCabe 40e1fac1c2 Implement Swarm update handler using PUT
This commit implements an update handler for Docker Swarm, it queries the
current spec, updates values in-situ before calling ServiceUpdate.

The UpdateConfig FailureAction is set to rollback, so in the event of
supplying values to the update that would result in the service failing
then the update will be rolled back.

The UpdateConfig Parallelism param is set to an explicit value of 1 which
will result in functions being updated 1 by 1 rather than all at once.

It also moves the restartDelay declaration out of the create and update
handlers and into the main server function alongside maxRestarts.

And finally this commit uses the PUT HTTP verb for updates rather than
the non-HTTP UPDATE verb which was being used initially (also adding it
to the Swagger definition).

Signed-off-by: John McCabe <john@johnmccabe.net>
2017-09-27 09:58:03 +01:00

120 lines
3.3 KiB
Go

package handlers
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"time"
"github.com/alexellis/faas/gateway/metrics"
"github.com/alexellis/faas/gateway/requests"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
)
// MakeUpdateFunctionHandler request to update an existing function with new configuration such as image, envvars etc.
func MakeUpdateFunctionHandler(metricsOptions metrics.MetricOptions, c *client.Client, maxRestarts uint64, restartDelay time.Duration) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := context.Background()
defer r.Body.Close()
body, _ := ioutil.ReadAll(r.Body)
request := requests.CreateFunctionRequest{}
err := json.Unmarshal(body, &request)
if err != nil {
log.Println("Error parsing request:", err)
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
serviceInspectopts := types.ServiceInspectOptions{
InsertDefaults: true,
}
service, _, err := c.ServiceInspectWithRaw(ctx, request.Service, serviceInspectopts)
if err != nil {
log.Println("Error inspecting service", err)
w.WriteHeader(http.StatusNotFound)
w.Write([]byte(err.Error()))
return
}
updateSpec(&request, &service.Spec, maxRestarts, restartDelay)
updateOpts := types.ServiceUpdateOptions{}
updateOpts.RegistryAuthFrom = types.RegistryAuthFromSpec
if len(request.RegistryAuth) > 0 {
auth, err := BuildEncodedAuthConfig(request.RegistryAuth, request.Image)
if err != nil {
log.Println("Error building registry auth configuration:", err)
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Invalid registry auth"))
return
}
updateOpts.EncodedRegistryAuth = auth
}
response, err := c.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, updateOpts)
if err != nil {
log.Println("Error updating service:", err)
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Update error: " + err.Error()))
return
}
log.Println(response.Warnings)
}
}
func updateSpec(request *requests.CreateFunctionRequest, spec *swarm.ServiceSpec, maxRestarts uint64, restartDelay time.Duration) {
constraints := []string{}
if request.Constraints != nil && len(request.Constraints) > 0 {
constraints = request.Constraints
} else {
constraints = linuxOnlyConstraints
}
nets := []swarm.NetworkAttachmentConfig{
{Target: request.Network},
}
spec.TaskTemplate.RestartPolicy.MaxAttempts = &maxRestarts
spec.TaskTemplate.RestartPolicy.Condition = swarm.RestartPolicyConditionAny
spec.TaskTemplate.RestartPolicy.Delay = &restartDelay
spec.TaskTemplate.ContainerSpec.Image = request.Image
spec.TaskTemplate.ContainerSpec.Labels = map[string]string{
"function": "true",
"uid": fmt.Sprintf("%d", time.Now().Nanosecond()),
}
spec.TaskTemplate.Networks = nets
spec.TaskTemplate.Placement = &swarm.Placement{
Constraints: constraints,
}
spec.Annotations = swarm.Annotations{
Name: request.Service,
}
spec.RollbackConfig = &swarm.UpdateConfig{
FailureAction: "pause",
}
spec.UpdateConfig = &swarm.UpdateConfig{
Parallelism: 1,
FailureAction: "rollback",
}
env := buildEnv(request.EnvProcess, request.EnvVars)
if len(env) > 0 {
spec.TaskTemplate.ContainerSpec.Env = env
}
}