Skip to content
This repository has been archived by the owner on Mar 18, 2021. It is now read-only.

Commit

Permalink
Allows configuration of CPU arch via argument and job constraint
Browse files Browse the repository at this point in the history
additionally allows configuration of other job constraints

Squashed commit of the following:

commit 7d9db5f
Author: cryptobias <[email protected]>
Date:   Thu Jun 13 10:50:41 2019 +0200

    Refactor CreateDataCenters to use Fields

    so it is consistent with CreateConstraints

    There is a small change of behaviour:
    This does not panic if there is no == or no value following it, but
    ignores the entry instead.

commit c6941af
Author: cryptobias <[email protected]>
Date:   Thu Jun 13 10:44:32 2019 +0200

    Use limit instead of constraint where applicable

commit 714f269
Author: cryptobias <[email protected]>
Date:   Thu Jun 13 10:31:59 2019 +0200

    Document Nomad job constraints in README.md

commit ddbab0c
Author: cryptobias <[email protected]>
Date:   Sun Jun 9 16:32:11 2019 +0200

    Allows to specify custom job constraints via the request

    The constraints have to be provided in the format:

    "<attribute> <operator> <value>"

    e.g.

    "${attr.cpu.arch} = arm"

    For compatibility the interpolation notation (`${}`) can be left out and `==` instead of `=` is supported.

    With this implementation the attribute and the operator can not contain
    spaces.

commit e440ccf
Author: cryptobias <[email protected]>
Date:   Thu Jun 6 10:43:45 2019 +0200

    Allow to change CPU arch constraint via commandline argument

Signed-off-by: cryptobias <[email protected]>
  • Loading branch information
cryptobias authored and acornies committed Jun 28, 2019
1 parent e516e7a commit 546fc20
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 22 deletions.
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,8 @@ $ echo "Nic" | faas-cli --gateway http://192.168.1.113:8080/ invoke gofunction
That is all there is to it, checkout the OpenFaaS community page for some inspiration and other demos.
[faas/community.md at master · openfaas/faas · GitHub](https://github.com/openfaas/faas/blob/master/community.md)
### Datacenters and Constraints
By default, the Nomad provider will use the datacenter of the Nomad agent or `dc1`. This can be overridden by setting one or more constraints `datacenter == value`. Constraints for limiting CPU and memory can also be set `memory` is an integer representing Megabytes, `cpu` is an integer representing MHz of CPU where 1024 equals one core.
### Datacenters and Limits
By default, the Nomad provider will use the datacenter of the Nomad agent or `dc1`. This can be overridden by setting one or more constraints `datacenter == value`. Limits for CPU and memory can also be set `memory` is an integer representing Megabytes, `cpu` is an integer representing MHz of CPU where 1024 equals one core.
i.e.
```bash
Expand All @@ -287,6 +287,19 @@ functions:
"datacenter == test1"
```
### Nomad Job Constraints
Additionally to the `datacenter` constraint [Nomad job constraints](https://www.nomadproject.io/docs/job-specification/constraint.html) are supported.
i.e.
```bash
$ faas-cli deploy --constraint '${attr.cpu.arch} = arm'
```
For compatibility and convenience the interpolation notation (`${}`) can be left out and `==` instead of `=` is supported.
All provided constraints are applied to the job (not the group or the task).
Leaving out the a field (.e.g. `${meta.foo} is_set`) or using more than one operator (e.g. `${meta.foo} is_set = bar`) is currently __not supported__.
### Annotations
Metadata can be added to the Nomad job definition through the use of the OpenFaaS annotation config. The below example would add the key `git` to the `Meta` section of nomad job definition which can be accessed through the API.
Expand Down
76 changes: 65 additions & 11 deletions handlers/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
Expand All @@ -29,8 +30,7 @@ var (
ephemeralDiskSize = 20

// Constraints
constraintCPUArch = "amd64"
taskMemory = 128
taskMemory = 128

// Update Strategy
updateAutoRevert = true
Expand Down Expand Up @@ -84,13 +84,12 @@ func createJob(r requests.CreateFunctionRequest, providerConfig types.ProviderCo
job.Update = createUpdateStrategy()

// add constraints
job.Constraints = append(job.Constraints,
&api.Constraint{
LTarget: "${attr.cpu.arch}",
Operand: "=",
RTarget: constraintCPUArch,
},
)
job.Constraints = append(job.Constraints, createConstraints(r)...)

cpuArchConstraint := createMissingCPUArchConstraint(job.Constraints, providerConfig.CPUArchConstraint)
if cpuArchConstraint != nil {
job.Constraints = append(job.Constraints, cpuArchConstraint)
}

job.TaskGroups = createTaskGroup(r, providerConfig)

Expand Down Expand Up @@ -241,9 +240,13 @@ func createDataCenters(r requests.CreateFunctionRequest, defaultDC string) []str
dcs := []string{}

for _, constr := range r.Constraints {
if strings.Contains(constr, "datacenter") {
dcs = append(dcs, strings.Trim(strings.Split(constr, "==")[1], " "))
fields := strings.Fields(constr)

if len(fields) != 3 || !strings.Contains(fields[0], "datacenter") || fields[1] != "==" {
continue
}

dcs = append(dcs, fields[2])
}

return dcs
Expand All @@ -253,6 +256,57 @@ func createDataCenters(r requests.CreateFunctionRequest, defaultDC string) []str
return []string{defaultDC}
}

func createConstraints(r requests.CreateFunctionRequest) []*api.Constraint {
constraints := make([]*api.Constraint, 0, len(r.Constraints))

if r.Constraints == nil {
return constraints
}

for _, requestConstraint := range r.Constraints {
fields := strings.Fields(requestConstraint)

if len(fields) < 3 || strings.Contains(fields[0], "datacenter") {
continue
}

attribute := fields[0]
operator := fields[1]
value := strings.Join(fields[2:], " ")

match, _ := regexp.MatchString("^\\${.*}$", attribute)
if !match {
attribute = fmt.Sprintf("${%v}", attribute)
}

if operator == "==" {
operator = "="
}

constraints = append(constraints, &api.Constraint{
LTarget: attribute,
Operand: operator,
RTarget: value,
})
}

return constraints
}

func createMissingCPUArchConstraint(constraints []*api.Constraint, defaultCPUArch string) *api.Constraint {
for _, constraint := range constraints {
if constraint.LTarget == "${attr.cpu.arch}" {
return nil
}
}

return &api.Constraint{
LTarget: "${attr.cpu.arch}",
Operand: "=",
RTarget: defaultCPUArch,
}
}

func createEnvVars(r requests.CreateFunctionRequest) map[string]string {
envVars := map[string]string{}

Expand Down
133 changes: 132 additions & 1 deletion handlers/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func setupDeploy(body string) (http.HandlerFunc, *httptest.ResponseRecorder, *ht

logger := hclog.Default()

return MakeDeploy(mockJob, fntypes.ProviderConfig{Vault: fntypes.VaultConfig{DefaultPolicy: "openfaas", SecretPathPrefix: "secret/openfaas"}, Datacenter: "dc1", ConsulAddress: "http://localhost:8500", ConsulDNSEnabled: true}, logger, mockStats),
return MakeDeploy(mockJob, fntypes.ProviderConfig{Vault: fntypes.VaultConfig{DefaultPolicy: "openfaas", SecretPathPrefix: "secret/openfaas"}, Datacenter: "dc1", ConsulAddress: "http://localhost:8500", ConsulDNSEnabled: true, CPUArchConstraint: "amd64"}, logger, mockStats),
httptest.NewRecorder(),
httptest.NewRequest("GET", "/system/functions", bytes.NewReader([]byte(body)))
}
Expand Down Expand Up @@ -224,3 +224,134 @@ func TestHandleDeployWithRegistryAuth(t *testing.T) {
assert.Equal(t, "username", auth[0]["username"])
assert.Equal(t, "password", auth[0]["password"])
}

func TestHandlesRequestUsingDefaultCPUArchConstraint(t *testing.T) {
fr := createRequest()
expectedCpuArchConstraint := api.Constraint{
LTarget: "${attr.cpu.arch}",
Operand: "=",
RTarget: "amd64",
}

h, rw, r := setupDeploy(fr.String())

h(rw, r)

args := mockJob.Calls[0].Arguments
job := args.Get(0).(*api.Job)
constraints := job.Constraints

assert.Equal(t, expectedCpuArchConstraint, *constraints[0])
}

func TestHandlesCpuArchConstraintFromRequest(t *testing.T) {
fr := createRequest()
fr.Constraints = []string{"${attr.cpu.arch} = arm"}
expectedCpuArchConstraint := api.Constraint{
LTarget: "${attr.cpu.arch}",
Operand: "=",
RTarget: "arm",
}

h, rw, r := setupDeploy(fr.String())

h(rw, r)

args := mockJob.Calls[0].Arguments
job := args.Get(0).(*api.Job)
constraints := job.Constraints

assert.Equal(t, expectedCpuArchConstraint, *constraints[0])
}

func TestHandlesConstraintsWithSpaces(t *testing.T) {
fr := createRequest()
fr.Constraints = []string{"${attr.cpu.arch} = not a real architecture"}
expectedCpuArchConstraint := api.Constraint{
LTarget: "${attr.cpu.arch}",
Operand: "=",
RTarget: "not a real architecture",
}

h, rw, r := setupDeploy(fr.String())

h(rw, r)

args := mockJob.Calls[0].Arguments
job := args.Get(0).(*api.Job)
constraints := job.Constraints

assert.Equal(t, expectedCpuArchConstraint, *constraints[0])
}

func TestHandlesConstraintsWithoutInterpolationNotation(t *testing.T) {
fr := createRequest()
fr.Constraints = []string{"attr.cpu.arch = arm"}
expectedCpuArchConstraint := api.Constraint{
LTarget: "${attr.cpu.arch}",
Operand: "=",
RTarget: "arm",
}

h, rw, r := setupDeploy(fr.String())

h(rw, r)

args := mockJob.Calls[0].Arguments
job := args.Get(0).(*api.Job)
constraints := job.Constraints

assert.Equal(t, expectedCpuArchConstraint, *constraints[0])
}

func TestHandlesConstraintsWithTwoEqualSigns(t *testing.T) {
fr := createRequest()
fr.Constraints = []string{"attr.cpu.arch == arm"}
expectedCpuArchConstraint := api.Constraint{
LTarget: "${attr.cpu.arch}",
Operand: "=",
RTarget: "arm",
}

h, rw, r := setupDeploy(fr.String())

h(rw, r)

args := mockJob.Calls[0].Arguments
job := args.Get(0).(*api.Job)
constraints := job.Constraints

assert.Equal(t, expectedCpuArchConstraint, *constraints[0])
}

func TestConstraintsForDataCenterDoNotCreateAJobConstraint(t *testing.T) {
fr := createRequest()
fr.Constraints = []string{"something.datacenter == dc1"}

h, rw, r := setupDeploy(fr.String())

h(rw, r)

args := mockJob.Calls[0].Arguments
job := args.Get(0).(*api.Job)
constraints := job.Constraints

assert.Equal(t, 1, len(constraints))
assert.NotContains(t, "datacenter", constraints[0].RTarget)
}

func TestIncompleteConstraintsAreIgnored(t *testing.T) {
fr := createRequest()
fr.Constraints = []string{"something", "something ="}

h, rw, r := setupDeploy(fr.String())

h(rw, r)

args := mockJob.Calls[0].Arguments
job := args.Get(0).(*api.Job)
constraints := job.Constraints

assert.Equal(t, 1, len(constraints))
assert.NotContains(t, "something", constraints[0].RTarget)
}
10 changes: 6 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ var (
vaultSecretPathPrefix = flag.String("vault_secret_path_prefix", "secret/openfaas", "The Vault k/v path prefix used when secrets are deployed with a function")
vaultAppRoleID = flag.String("vault_app_role_id", "", "A valid Vault AppRole role_id")
vaultAppRoleSecretID = flag.String("vault_app_secret_id", "", "A valid Vault AppRole secret_id derived from the role")
cpuArchConstraint = flag.String("cpu_arch_constraint", "amd64", "CPU architecture to constraint deployed functions to")
)

var functionTimeout = flag.Duration("function_timeout", 30*time.Second, "Timeout for function execution")
Expand Down Expand Up @@ -127,10 +128,11 @@ func createFaaSHandlers(nomadClient *api.Client, consulResolver *consul.Resolver
vaultConfig.TLSSkipVerify = *vaultTLSSkipVerify

providerConfig := &fntypes.ProviderConfig{
Vault: vaultConfig,
Datacenter: datacenter,
ConsulAddress: *consulAddr,
ConsulDNSEnabled: *enableConsulDNS,
Vault: vaultConfig,
Datacenter: datacenter,
ConsulAddress: *consulAddr,
ConsulDNSEnabled: *enableConsulDNS,
CPUArchConstraint: *cpuArchConstraint,
}

vs := vault.NewVaultService(&vaultConfig, logger)
Expand Down
9 changes: 5 additions & 4 deletions types/provider_config.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package types

type ProviderConfig struct {
Vault VaultConfig
Datacenter string
ConsulAddress string
ConsulDNSEnabled bool
Vault VaultConfig
Datacenter string
ConsulAddress string
ConsulDNSEnabled bool
CPUArchConstraint string
}

0 comments on commit 546fc20

Please sign in to comment.