From ac1753a614c0105b3f73efa4a17cdf45aca37347 Mon Sep 17 00:00:00 2001 From: chrispruitt Date: Thu, 4 Apr 2024 05:54:04 -0400 Subject: [PATCH] Nomad provider to allow empty services --- docs/content/providers/nomad.md | 24 ++ .../reference/static-configuration/cli-ref.md | 3 + .../reference/static-configuration/env-ref.md | 3 + .../reference/static-configuration/file.toml | 1 + .../reference/static-configuration/file.yaml | 1 + pkg/provider/nomad/config.go | 21 +- pkg/provider/nomad/config_test.go | 337 +++++++++++++++ .../job_job1_WithGroupService_Scaling1.json | 220 ++++++++++ .../job_job2_WithGroupService_Scaling0.json | 220 ++++++++++ ...job3_WithGroupService_ScalingDisabled.json | 206 ++++++++++ ...hGroupService_ScalingDisabled_Stopped.json | 206 ++++++++++ ...ob_job5_WithGroupTaskService_Scaling1.json | 299 ++++++++++++++ ...ob_job6_WithGroupTaskService_Scaling0.json | 299 ++++++++++++++ pkg/provider/nomad/fixtures/job_job7_TCP.json | 222 ++++++++++ pkg/provider/nomad/fixtures/job_job8_UDP.json | 221 ++++++++++ .../job_job9_ScalingEnabled_Stopped.json | 220 ++++++++++ pkg/provider/nomad/fixtures/jobs_job1.json | 56 +++ pkg/provider/nomad/fixtures/jobs_job2.json | 47 +++ pkg/provider/nomad/fixtures/jobs_job3.json | 47 +++ pkg/provider/nomad/fixtures/jobs_job4.json | 47 +++ pkg/provider/nomad/fixtures/jobs_job5.json | 56 +++ pkg/provider/nomad/fixtures/jobs_job6.json | 47 +++ pkg/provider/nomad/fixtures/jobs_job7.json | 47 +++ pkg/provider/nomad/fixtures/jobs_job8.json | 47 +++ pkg/provider/nomad/fixtures/jobs_job9.json | 47 +++ .../nomad/fixtures/service_hello.json | 20 + pkg/provider/nomad/fixtures/service_job1.json | 18 + pkg/provider/nomad/fixtures/service_job2.json | 1 + pkg/provider/nomad/fixtures/service_job3.json | 18 + pkg/provider/nomad/fixtures/service_job4.json | 1 + .../nomad/fixtures/service_job5task1.json | 18 + .../nomad/fixtures/service_job5task2.json | 18 + .../nomad/fixtures/service_job6task1.json | 1 + .../nomad/fixtures/service_job6task2.json | 1 + pkg/provider/nomad/fixtures/service_job7.json | 20 + pkg/provider/nomad/fixtures/service_job8.json | 19 + pkg/provider/nomad/fixtures/service_job9.json | 1 + .../nomad/fixtures/service_redis.json | 18 + pkg/provider/nomad/fixtures/services.json | 21 + pkg/provider/nomad/nomad.go | 124 +++++- pkg/provider/nomad/nomad_test.go | 384 ++++++++++++++---- 41 files changed, 3540 insertions(+), 87 deletions(-) create mode 100644 pkg/provider/nomad/fixtures/job_job1_WithGroupService_Scaling1.json create mode 100644 pkg/provider/nomad/fixtures/job_job2_WithGroupService_Scaling0.json create mode 100644 pkg/provider/nomad/fixtures/job_job3_WithGroupService_ScalingDisabled.json create mode 100644 pkg/provider/nomad/fixtures/job_job4_WithGroupService_ScalingDisabled_Stopped.json create mode 100644 pkg/provider/nomad/fixtures/job_job5_WithGroupTaskService_Scaling1.json create mode 100644 pkg/provider/nomad/fixtures/job_job6_WithGroupTaskService_Scaling0.json create mode 100644 pkg/provider/nomad/fixtures/job_job7_TCP.json create mode 100644 pkg/provider/nomad/fixtures/job_job8_UDP.json create mode 100644 pkg/provider/nomad/fixtures/job_job9_ScalingEnabled_Stopped.json create mode 100644 pkg/provider/nomad/fixtures/jobs_job1.json create mode 100644 pkg/provider/nomad/fixtures/jobs_job2.json create mode 100644 pkg/provider/nomad/fixtures/jobs_job3.json create mode 100644 pkg/provider/nomad/fixtures/jobs_job4.json create mode 100644 pkg/provider/nomad/fixtures/jobs_job5.json create mode 100644 pkg/provider/nomad/fixtures/jobs_job6.json create mode 100644 pkg/provider/nomad/fixtures/jobs_job7.json create mode 100644 pkg/provider/nomad/fixtures/jobs_job8.json create mode 100644 pkg/provider/nomad/fixtures/jobs_job9.json create mode 100644 pkg/provider/nomad/fixtures/service_hello.json create mode 100644 pkg/provider/nomad/fixtures/service_job1.json create mode 100644 pkg/provider/nomad/fixtures/service_job2.json create mode 100644 pkg/provider/nomad/fixtures/service_job3.json create mode 100644 pkg/provider/nomad/fixtures/service_job4.json create mode 100644 pkg/provider/nomad/fixtures/service_job5task1.json create mode 100644 pkg/provider/nomad/fixtures/service_job5task2.json create mode 100644 pkg/provider/nomad/fixtures/service_job6task1.json create mode 100644 pkg/provider/nomad/fixtures/service_job6task2.json create mode 100644 pkg/provider/nomad/fixtures/service_job7.json create mode 100644 pkg/provider/nomad/fixtures/service_job8.json create mode 100644 pkg/provider/nomad/fixtures/service_job9.json create mode 100644 pkg/provider/nomad/fixtures/service_redis.json create mode 100644 pkg/provider/nomad/fixtures/services.json diff --git a/docs/content/providers/nomad.md b/docs/content/providers/nomad.md index f1d0d1fe1..85b3ac033 100644 --- a/docs/content/providers/nomad.md +++ b/docs/content/providers/nomad.md @@ -511,3 +511,27 @@ providers: --providers.nomad.namespaces=ns1,ns2 # ... ``` + +### `allowEmptyServices` + +_Optional, Default: false_ + +If the parameter is set to `true`, +it allows the creation of an empty [servers load balancer](../routing/services/index.md#servers-load-balancer) if the targeted Nomad service has no endpoints available. This results in a `503` HTTP response instead of a `404`. + +```yaml tab="File (YAML)" +providers: + nomad: + allowEmptyServices: true + # ... +``` + +```toml tab="File (TOML)" +[providers.nomad] + allowEmptyServices = true + # ... +``` + +```bash tab="CLI" +--providers.nomad.allowEmptyServices=true +``` diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index a632402ad..6194dfbfe 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -789,6 +789,9 @@ Kubernetes bearer token (not needed for in-cluster client). It accepts either a `--providers.nomad`: Enable Nomad backend with default settings. (Default: ```false```) +`--providers.nomad.allowemptyservices`: +Allow the creation of services without endpoints. (Default: ```false```) + `--providers.nomad.constraints`: Constraints is an expression that Traefik matches against the Nomad service's tags to determine whether to create route(s) for that service. diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index 3fe8614e5..7fcde07fc 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -789,6 +789,9 @@ Kubernetes bearer token (not needed for in-cluster client). It accepts either a `TRAEFIK_PROVIDERS_NOMAD`: Enable Nomad backend with default settings. (Default: ```false```) +`TRAEFIK_PROVIDERS_NOMAD_ALLOWEMPTYSERVICES`: +Allow the creation of services without endpoints. (Default: ```false```) + `TRAEFIK_PROVIDERS_NOMAD_CONSTRAINTS`: Constraints is an expression that Traefik matches against the Nomad service's tags to determine whether to create route(s) for that service. diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index 6bf62c9cf..e5a6e379e 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -185,6 +185,7 @@ stale = true exposedByDefault = true refreshInterval = "42s" + allowEmptyServices = true namespaces = ["foobar", "foobar"] [providers.nomad.endpoint] address = "foobar" diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index d54e06b60..362418fbc 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -216,6 +216,7 @@ providers: stale: true exposedByDefault: true refreshInterval: 42s + allowEmptyServices: true namespaces: - foobar - foobar diff --git a/pkg/provider/nomad/config.go b/pkg/provider/nomad/config.go index b6caa8cf1..83022f31d 100644 --- a/pkg/provider/nomad/config.go +++ b/pkg/provider/nomad/config.go @@ -97,8 +97,11 @@ func (p *Provider) buildTCPConfig(i item, configuration *dynamic.TCPConfiguratio } for _, service := range configuration.Services { - if err := p.addServerTCP(i, service.LoadBalancer); err != nil { - return err + // Leave load balancer empty when no address and allowEmptyServices = true + if !(i.Address == "" && p.AllowEmptyServices) { + if err := p.addServerTCP(i, service.LoadBalancer); err != nil { + return err + } } } @@ -115,8 +118,11 @@ func (p *Provider) buildUDPConfig(i item, configuration *dynamic.UDPConfiguratio } for _, service := range configuration.Services { - if err := p.addServerUDP(i, service.LoadBalancer); err != nil { - return err + // Leave load balancer empty when no address and allowEmptyServices = true + if !(i.Address == "" && p.AllowEmptyServices) { + if err := p.addServerUDP(i, service.LoadBalancer); err != nil { + return err + } } } @@ -136,8 +142,11 @@ func (p *Provider) buildServiceConfig(i item, configuration *dynamic.HTTPConfigu } for _, service := range configuration.Services { - if err := p.addServer(i, service.LoadBalancer); err != nil { - return err + // Leave load balancer empty when no address and allowEmptyServices = true + if !(i.Address == "" && p.AllowEmptyServices) { + if err := p.addServer(i, service.LoadBalancer); err != nil { + return err + } } } diff --git a/pkg/provider/nomad/config_test.go b/pkg/provider/nomad/config_test.go index e1ec061e5..2f036e929 100644 --- a/pkg/provider/nomad/config_test.go +++ b/pkg/provider/nomad/config_test.go @@ -706,6 +706,42 @@ func Test_buildConfig(t *testing.T) { }, }, }, + { + desc: "empty service", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.enable=true", + }, + Address: "", + Port: -1, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, { desc: "one service with rule label", items: []item{ @@ -2825,6 +2861,307 @@ func Test_buildConfig(t *testing.T) { } } +func Test_buildConfigAllowEmptyServicesTrue(t *testing.T) { + testCases := []struct { + desc string + items []item + constraints string + expected *dynamic.Configuration + }{ + { + desc: "empty service http", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.enable=true", + }, + Address: "", + Port: -1, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "Test": { + Service: "Test", + Rule: "Host(`Test.traefik.test`)", + DefaultRule: true, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "Test": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: nil, + PassHostHeader: Bool(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "empty service tcp", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.tcp.routers.test.rule = HostSNI(`foobar`)", + }, + Address: "", + Port: -1, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "test": { + Rule: "HostSNI(`foobar`)", + Service: "Test", + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "Test": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{}, + }, + }, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "empty service udp", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.udp.routers.test.entrypoints = udp", + }, + Address: "", + Port: -1, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{ + "test": { + EntryPoints: []string{"udp"}, + Service: "Test", + }, + }, + Services: map[string]*dynamic.UDPService{ + "Test": { + LoadBalancer: &dynamic.UDPServersLoadBalancer{}, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + p := new(Provider) + p.SetDefaults() + p.AllowEmptyServices = true + p.DefaultRule = "Host(`{{ normalize .Name }}.traefik.test`)" + p.Constraints = test.constraints + err := p.Init() + require.NoError(t, err) + + ctx := context.TODO() + c := p.buildConfig(ctx, test.items) + require.Equal(t, test.expected, c) + }) + } +} + +func Test_buildConfigAllowEmptyServicesFalseDefault(t *testing.T) { + testCases := []struct { + desc string + items []item + constraints string + expected *dynamic.Configuration + }{ + { + desc: "empty service http", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.enable=true", + }, + Address: "", + Port: -1, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "empty service tcp", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.tcp.routers.test.rule = HostSNI(`foobar`)", + }, + Address: "", + Port: -1, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + { + desc: "empty service udp", + items: []item{ + { + ID: "id1", + Name: "Test", + Tags: []string{ + "traefik.udp.routers.test.entrypoints = udp", + }, + Address: "", + Port: -1, + ExtraConf: configuration{Enable: true}, + }, + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Stores: map[string]tls.Store{}, + }, + }, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + p := new(Provider) + p.SetDefaults() + p.DefaultRule = "Host(`{{ normalize .Name }}.traefik.test`)" + p.Constraints = test.constraints + err := p.Init() + require.NoError(t, err) + + ctx := context.TODO() + c := p.buildConfig(ctx, test.items) + require.Equal(t, test.expected, c) + }) + } +} + func Test_keepItem(t *testing.T) { testCases := []struct { name string diff --git a/pkg/provider/nomad/fixtures/job_job1_WithGroupService_Scaling1.json b/pkg/provider/nomad/fixtures/job_job1_WithGroupService_Scaling1.json new file mode 100644 index 000000000..9044736c6 --- /dev/null +++ b/pkg/provider/nomad/fixtures/job_job1_WithGroupService_Scaling1.json @@ -0,0 +1,220 @@ +{ + "Stop": false, + "Region": "global", + "Namespace": "default", + "ID": "job1", + "ParentID": "", + "Name": "job1", + "Type": "service", + "Priority": 50, + "AllAtOnce": false, + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Constraints": null, + "Affinities": null, + "Spreads": null, + "TaskGroups": [ + { + "Name": "group1", + "Count": 1, + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000, + "ProgressDeadline": 600000000000, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Migrate": { + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000 + }, + "Constraints": [ + { + "LTarget": "${attr.nomad.service_discovery}", + "RTarget": "true", + "Operand": "=" + } + ], + "Scaling": { + "ID": "654cb8ee-9c81-4fe8-02a0-2aecdea35ae3", + "Type": "horizontal", + "Target": { + "Job": "job1", + "Group": "group1", + "Namespace": "default" + }, + "Policy": null, + "Min": 0, + "Max": 3, + "Enabled": true, + "CreateIndex": 0, + "ModifyIndex": 0 + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "Tasks": [ + { + "Name": "task1", + "Driver": "docker", + "User": "", + "Config": { + "image": "nginx", + "ports": [ + "http" + ] + }, + "Env": null, + "Services": null, + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + } + ], + "EphemeralDisk": { + "Sticky": false, + "SizeMB": 300, + "Migrate": false + }, + "Meta": null, + "ReschedulePolicy": { + "Attempts": 0, + "Interval": 0, + "Delay": 30000000000, + "DelayFunction": "exponential", + "MaxDelay": 3600000000000, + "Unlimited": true + }, + "Affinities": null, + "Spreads": null, + "Networks": [ + { + "Mode": "", + "Device": "", + "CIDR": "", + "IP": "", + "Hostname": "", + "MBits": 0, + "DNS": null, + "ReservedPorts": null, + "DynamicPorts": [ + { + "Label": "http", + "Value": 0, + "To": 80, + "HostNetwork": "default" + } + ] + } + ], + "Consul": { + "Namespace": "" + }, + "Services": [ + { + "Name": "job1", + "TaskName": "", + "PortLabel": "http", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Volumes": null, + "ShutdownDelay": null, + "StopAfterClientDisconnect": null, + "MaxClientDisconnect": null + } + ], + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "", + "MinHealthyTime": 0, + "HealthyDeadline": 0, + "ProgressDeadline": 0, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Multiregion": null, + "Periodic": null, + "ParameterizedJob": null, + "Dispatched": false, + "DispatchIdempotencyToken": "", + "Payload": null, + "Meta": null, + "ConsulToken": "", + "ConsulNamespace": "", + "VaultToken": "", + "VaultNamespace": "", + "NomadTokenID": "", + "Status": "running", + "StatusDescription": "", + "Stable": true, + "Version": 11, + "SubmitTime": 1705690395733241600, + "CreateIndex": 493, + "ModifyIndex": 9961, + "JobModifyIndex": 9955 +} \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/job_job2_WithGroupService_Scaling0.json b/pkg/provider/nomad/fixtures/job_job2_WithGroupService_Scaling0.json new file mode 100644 index 000000000..0cf8656f7 --- /dev/null +++ b/pkg/provider/nomad/fixtures/job_job2_WithGroupService_Scaling0.json @@ -0,0 +1,220 @@ +{ + "Stop": false, + "Region": "global", + "Namespace": "default", + "ID": "job2", + "ParentID": "", + "Name": "job2", + "Type": "service", + "Priority": 50, + "AllAtOnce": false, + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Constraints": null, + "Affinities": null, + "Spreads": null, + "TaskGroups": [ + { + "Name": "group1", + "Count": 0, + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000, + "ProgressDeadline": 600000000000, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Migrate": { + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000 + }, + "Constraints": [ + { + "LTarget": "${attr.nomad.service_discovery}", + "RTarget": "true", + "Operand": "=" + } + ], + "Scaling": { + "ID": "0ae1d8fa-aa84-0b1b-941f-82dc71bc4664", + "Type": "horizontal", + "Target": { + "Group": "group1", + "Namespace": "default", + "Job": "job2" + }, + "Policy": null, + "Min": 0, + "Max": 3, + "Enabled": true, + "CreateIndex": 9975, + "ModifyIndex": 9975 + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "Tasks": [ + { + "Name": "task1", + "Driver": "docker", + "User": "", + "Config": { + "ports": [ + "http" + ], + "image": "nginx" + }, + "Env": null, + "Services": null, + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + } + ], + "EphemeralDisk": { + "Sticky": false, + "SizeMB": 300, + "Migrate": false + }, + "Meta": null, + "ReschedulePolicy": { + "Attempts": 0, + "Interval": 0, + "Delay": 30000000000, + "DelayFunction": "exponential", + "MaxDelay": 3600000000000, + "Unlimited": true + }, + "Affinities": null, + "Spreads": null, + "Networks": [ + { + "Mode": "", + "Device": "", + "CIDR": "", + "IP": "", + "Hostname": "", + "MBits": 0, + "DNS": null, + "ReservedPorts": null, + "DynamicPorts": [ + { + "Label": "http", + "Value": 0, + "To": 80, + "HostNetwork": "default" + } + ] + } + ], + "Consul": { + "Namespace": "" + }, + "Services": [ + { + "Name": "job2", + "TaskName": "", + "PortLabel": "http", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Volumes": null, + "ShutdownDelay": null, + "StopAfterClientDisconnect": null, + "MaxClientDisconnect": null + } + ], + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "", + "MinHealthyTime": 0, + "HealthyDeadline": 0, + "ProgressDeadline": 0, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Multiregion": null, + "Periodic": null, + "ParameterizedJob": null, + "Dispatched": false, + "DispatchIdempotencyToken": "", + "Payload": null, + "Meta": null, + "ConsulToken": "", + "ConsulNamespace": "", + "VaultToken": "", + "VaultNamespace": "", + "NomadTokenID": "", + "Status": "dead", + "StatusDescription": "", + "Stable": true, + "Version": 10, + "SubmitTime": 1705690880440177400, + "CreateIndex": 2923, + "ModifyIndex": 10048, + "JobModifyIndex": 10044 +} \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/job_job3_WithGroupService_ScalingDisabled.json b/pkg/provider/nomad/fixtures/job_job3_WithGroupService_ScalingDisabled.json new file mode 100644 index 000000000..a0efc5e16 --- /dev/null +++ b/pkg/provider/nomad/fixtures/job_job3_WithGroupService_ScalingDisabled.json @@ -0,0 +1,206 @@ +{ + "Stop": false, + "Region": "global", + "Namespace": "default", + "ID": "job3", + "ParentID": "", + "Name": "job3", + "Type": "service", + "Priority": 50, + "AllAtOnce": false, + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Constraints": null, + "Affinities": null, + "Spreads": null, + "TaskGroups": [ + { + "Name": "group1", + "Count": 1, + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000, + "ProgressDeadline": 600000000000, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Migrate": { + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000 + }, + "Constraints": [ + { + "LTarget": "${attr.nomad.service_discovery}", + "RTarget": "true", + "Operand": "=" + } + ], + "Scaling": null, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "Tasks": [ + { + "Name": "task1", + "Driver": "docker", + "User": "", + "Config": { + "ports": [ + "http" + ], + "image": "nginx" + }, + "Env": null, + "Services": null, + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + } + ], + "EphemeralDisk": { + "Sticky": false, + "SizeMB": 300, + "Migrate": false + }, + "Meta": null, + "ReschedulePolicy": { + "Attempts": 0, + "Interval": 0, + "Delay": 30000000000, + "DelayFunction": "exponential", + "MaxDelay": 3600000000000, + "Unlimited": true + }, + "Affinities": null, + "Spreads": null, + "Networks": [ + { + "Mode": "", + "Device": "", + "CIDR": "", + "IP": "", + "Hostname": "", + "MBits": 0, + "DNS": null, + "ReservedPorts": null, + "DynamicPorts": [ + { + "Label": "http", + "Value": 0, + "To": 80, + "HostNetwork": "default" + } + ] + } + ], + "Consul": { + "Namespace": "" + }, + "Services": [ + { + "Name": "job3", + "TaskName": "", + "PortLabel": "http", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Volumes": null, + "ShutdownDelay": null, + "StopAfterClientDisconnect": null, + "MaxClientDisconnect": null + } + ], + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "", + "MinHealthyTime": 0, + "HealthyDeadline": 0, + "ProgressDeadline": 0, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Multiregion": null, + "Periodic": null, + "ParameterizedJob": null, + "Dispatched": false, + "DispatchIdempotencyToken": "", + "Payload": null, + "Meta": null, + "ConsulToken": "", + "ConsulNamespace": "", + "VaultToken": "", + "VaultNamespace": "", + "NomadTokenID": "", + "Status": "running", + "StatusDescription": "", + "Stable": true, + "Version": 4, + "SubmitTime": 1705690892484556300, + "CreateIndex": 2932, + "ModifyIndex": 9992, + "JobModifyIndex": 9983 +} \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/job_job4_WithGroupService_ScalingDisabled_Stopped.json b/pkg/provider/nomad/fixtures/job_job4_WithGroupService_ScalingDisabled_Stopped.json new file mode 100644 index 000000000..55d312dcd --- /dev/null +++ b/pkg/provider/nomad/fixtures/job_job4_WithGroupService_ScalingDisabled_Stopped.json @@ -0,0 +1,206 @@ +{ + "Stop": true, + "Region": "global", + "Namespace": "default", + "ID": "job4", + "ParentID": "", + "Name": "job4", + "Type": "service", + "Priority": 50, + "AllAtOnce": false, + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Constraints": null, + "Affinities": null, + "Spreads": null, + "TaskGroups": [ + { + "Name": "group1", + "Count": 1, + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000, + "ProgressDeadline": 600000000000, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Migrate": { + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000 + }, + "Constraints": [ + { + "LTarget": "${attr.nomad.service_discovery}", + "RTarget": "true", + "Operand": "=" + } + ], + "Scaling": null, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "Tasks": [ + { + "Name": "task1", + "Driver": "docker", + "User": "", + "Config": { + "image": "nginx", + "ports": [ + "http" + ] + }, + "Env": null, + "Services": null, + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + } + ], + "EphemeralDisk": { + "Sticky": false, + "SizeMB": 300, + "Migrate": false + }, + "Meta": null, + "ReschedulePolicy": { + "Attempts": 0, + "Interval": 0, + "Delay": 30000000000, + "DelayFunction": "exponential", + "MaxDelay": 3600000000000, + "Unlimited": true + }, + "Affinities": null, + "Spreads": null, + "Networks": [ + { + "Mode": "", + "Device": "", + "CIDR": "", + "IP": "", + "Hostname": "", + "MBits": 0, + "DNS": null, + "ReservedPorts": null, + "DynamicPorts": [ + { + "Label": "http", + "Value": 0, + "To": 80, + "HostNetwork": "default" + } + ] + } + ], + "Consul": { + "Namespace": "" + }, + "Services": [ + { + "Name": "job4", + "TaskName": "", + "PortLabel": "http", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Volumes": null, + "ShutdownDelay": null, + "StopAfterClientDisconnect": null, + "MaxClientDisconnect": null + } + ], + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "", + "MinHealthyTime": 0, + "HealthyDeadline": 0, + "ProgressDeadline": 0, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Multiregion": null, + "Periodic": null, + "ParameterizedJob": null, + "Dispatched": false, + "DispatchIdempotencyToken": "", + "Payload": null, + "Meta": null, + "ConsulToken": "", + "ConsulNamespace": "", + "VaultToken": "", + "VaultNamespace": "", + "NomadTokenID": "", + "Status": "dead", + "StatusDescription": "", + "Stable": true, + "Version": 11, + "SubmitTime": 1705690905317339000, + "CreateIndex": 3079, + "ModifyIndex": 10054, + "JobModifyIndex": 10052 +} \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/job_job5_WithGroupTaskService_Scaling1.json b/pkg/provider/nomad/fixtures/job_job5_WithGroupTaskService_Scaling1.json new file mode 100644 index 000000000..f0171814d --- /dev/null +++ b/pkg/provider/nomad/fixtures/job_job5_WithGroupTaskService_Scaling1.json @@ -0,0 +1,299 @@ +{ + "Stop": false, + "Region": "global", + "Namespace": "default", + "ID": "job5", + "ParentID": "", + "Name": "job5", + "Type": "service", + "Priority": 50, + "AllAtOnce": false, + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Constraints": null, + "Affinities": null, + "Spreads": null, + "TaskGroups": [ + { + "Name": "group1", + "Count": 1, + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000, + "ProgressDeadline": 600000000000, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Migrate": { + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000 + }, + "Constraints": [ + { + "LTarget": "${attr.nomad.service_discovery}", + "RTarget": "true", + "Operand": "=" + } + ], + "Scaling": { + "ID": "56d57f83-7b9e-fbfa-af96-e2c7f5dc698c", + "Type": "horizontal", + "Target": { + "Namespace": "default", + "Job": "job5", + "Group": "group1" + }, + "Policy": null, + "Min": 0, + "Max": 3, + "Enabled": true, + "CreateIndex": 0, + "ModifyIndex": 0 + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "Tasks": [ + { + "Name": "task1", + "Driver": "docker", + "User": "", + "Config": { + "image": "nginx", + "ports": [ + "http" + ] + }, + "Env": null, + "Services": [ + { + "Name": "job5task1", + "TaskName": "task1", + "PortLabel": "http", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + }, + { + "Name": "task2", + "Driver": "docker", + "User": "", + "Config": { + "ports": [ + "other" + ], + "image": "nginx" + }, + "Env": null, + "Services": [ + { + "Name": "job5task2", + "TaskName": "task2", + "PortLabel": "other", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + } + ], + "EphemeralDisk": { + "Sticky": false, + "SizeMB": 300, + "Migrate": false + }, + "Meta": null, + "ReschedulePolicy": { + "Attempts": 0, + "Interval": 0, + "Delay": 30000000000, + "DelayFunction": "exponential", + "MaxDelay": 3600000000000, + "Unlimited": true + }, + "Affinities": null, + "Spreads": null, + "Networks": [ + { + "Mode": "", + "Device": "", + "CIDR": "", + "IP": "", + "Hostname": "", + "MBits": 0, + "DNS": null, + "ReservedPorts": null, + "DynamicPorts": [ + { + "Label": "http", + "Value": 0, + "To": 80, + "HostNetwork": "default" + }, + { + "Label": "other", + "Value": 0, + "To": 80, + "HostNetwork": "default" + } + ] + } + ], + "Consul": { + "Namespace": "" + }, + "Services": null, + "Volumes": null, + "ShutdownDelay": null, + "StopAfterClientDisconnect": null, + "MaxClientDisconnect": null + } + ], + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "", + "MinHealthyTime": 0, + "HealthyDeadline": 0, + "ProgressDeadline": 0, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Multiregion": null, + "Periodic": null, + "ParameterizedJob": null, + "Dispatched": false, + "DispatchIdempotencyToken": "", + "Payload": null, + "Meta": null, + "ConsulToken": "", + "ConsulNamespace": "", + "VaultToken": "", + "VaultNamespace": "", + "NomadTokenID": "", + "Status": "running", + "StatusDescription": "", + "Stable": true, + "Version": 7, + "SubmitTime": 1705690918813110300, + "CreateIndex": 3095, + "ModifyIndex": 10018, + "JobModifyIndex": 10008 +} \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/job_job6_WithGroupTaskService_Scaling0.json b/pkg/provider/nomad/fixtures/job_job6_WithGroupTaskService_Scaling0.json new file mode 100644 index 000000000..a4528e4dd --- /dev/null +++ b/pkg/provider/nomad/fixtures/job_job6_WithGroupTaskService_Scaling0.json @@ -0,0 +1,299 @@ +{ + "Stop": false, + "Region": "global", + "Namespace": "default", + "ID": "job6", + "ParentID": "", + "Name": "job6", + "Type": "service", + "Priority": 50, + "AllAtOnce": false, + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Constraints": null, + "Affinities": null, + "Spreads": null, + "TaskGroups": [ + { + "Name": "group1", + "Count": 0, + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000, + "ProgressDeadline": 600000000000, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Migrate": { + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000 + }, + "Constraints": [ + { + "LTarget": "${attr.nomad.service_discovery}", + "RTarget": "true", + "Operand": "=" + } + ], + "Scaling": { + "ID": "d18b02ae-5a68-e635-8100-06b110910b5f", + "Type": "horizontal", + "Target": { + "Job": "job6", + "Group": "group1", + "Namespace": "default" + }, + "Policy": null, + "Min": 0, + "Max": 3, + "Enabled": true, + "CreateIndex": 10020, + "ModifyIndex": 10020 + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "Tasks": [ + { + "Name": "task1", + "Driver": "docker", + "User": "", + "Config": { + "image": "nginx", + "ports": [ + "http" + ] + }, + "Env": null, + "Services": [ + { + "Name": "job6task1", + "TaskName": "task1", + "PortLabel": "http", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + }, + { + "Name": "task2", + "Driver": "docker", + "User": "", + "Config": { + "image": "nginx", + "ports": [ + "other" + ] + }, + "Env": null, + "Services": [ + { + "Name": "job6task2", + "TaskName": "task2", + "PortLabel": "other", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + } + ], + "EphemeralDisk": { + "Sticky": false, + "SizeMB": 300, + "Migrate": false + }, + "Meta": null, + "ReschedulePolicy": { + "Attempts": 0, + "Interval": 0, + "Delay": 30000000000, + "DelayFunction": "exponential", + "MaxDelay": 3600000000000, + "Unlimited": true + }, + "Affinities": null, + "Spreads": null, + "Networks": [ + { + "Mode": "", + "Device": "", + "CIDR": "", + "IP": "", + "Hostname": "", + "MBits": 0, + "DNS": null, + "ReservedPorts": null, + "DynamicPorts": [ + { + "Label": "http", + "Value": 0, + "To": 80, + "HostNetwork": "default" + }, + { + "Label": "other", + "Value": 0, + "To": 80, + "HostNetwork": "default" + } + ] + } + ], + "Consul": { + "Namespace": "" + }, + "Services": null, + "Volumes": null, + "ShutdownDelay": null, + "StopAfterClientDisconnect": null, + "MaxClientDisconnect": null + } + ], + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "", + "MinHealthyTime": 0, + "HealthyDeadline": 0, + "ProgressDeadline": 0, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Multiregion": null, + "Periodic": null, + "ParameterizedJob": null, + "Dispatched": false, + "DispatchIdempotencyToken": "", + "Payload": null, + "Meta": null, + "ConsulToken": "", + "ConsulNamespace": "", + "VaultToken": "", + "VaultNamespace": "", + "NomadTokenID": "", + "Status": "dead", + "StatusDescription": "", + "Stable": true, + "Version": 3, + "SubmitTime": 1705690931854150700, + "CreateIndex": 9941, + "ModifyIndex": 10078, + "JobModifyIndex": 10074 +} \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/job_job7_TCP.json b/pkg/provider/nomad/fixtures/job_job7_TCP.json new file mode 100644 index 000000000..3bf39a776 --- /dev/null +++ b/pkg/provider/nomad/fixtures/job_job7_TCP.json @@ -0,0 +1,222 @@ +{ + "Stop": false, + "Region": "global", + "Namespace": "default", + "ID": "job7", + "ParentID": "", + "Name": "job7", + "Type": "service", + "Priority": 50, + "AllAtOnce": false, + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Constraints": null, + "Affinities": null, + "Spreads": null, + "TaskGroups": [ + { + "Name": "group1", + "Count": 1, + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000, + "ProgressDeadline": 600000000000, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Migrate": { + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000 + }, + "Constraints": [ + { + "LTarget": "${attr.nomad.service_discovery}", + "RTarget": "true", + "Operand": "=" + } + ], + "Scaling": { + "ID": "e3b82307-1b7f-5fc6-901a-d9174418461f", + "Type": "horizontal", + "Target": { + "Namespace": "default", + "Job": "job7", + "Group": "group1" + }, + "Policy": null, + "Min": 0, + "Max": 3, + "Enabled": true, + "CreateIndex": 0, + "ModifyIndex": 0 + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "Tasks": [ + { + "Name": "task1", + "Driver": "docker", + "User": "", + "Config": { + "image": "nginx", + "ports": [ + "http" + ] + }, + "Env": null, + "Services": null, + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + } + ], + "EphemeralDisk": { + "Sticky": false, + "SizeMB": 300, + "Migrate": false + }, + "Meta": null, + "ReschedulePolicy": { + "Attempts": 0, + "Interval": 0, + "Delay": 30000000000, + "DelayFunction": "exponential", + "MaxDelay": 3600000000000, + "Unlimited": true + }, + "Affinities": null, + "Spreads": null, + "Networks": [ + { + "Mode": "", + "Device": "", + "CIDR": "", + "IP": "", + "Hostname": "", + "MBits": 0, + "DNS": null, + "ReservedPorts": null, + "DynamicPorts": [ + { + "Label": "http", + "Value": 0, + "To": 80, + "HostNetwork": "default" + } + ] + } + ], + "Consul": { + "Namespace": "" + }, + "Services": [ + { + "Name": "job7", + "TaskName": "", + "PortLabel": "http", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true", + "traefik.tcp.routers.job7.rule=HostSNI(`job7`)", + "traefik.tcp.routers.job7.tls.passthrough=true" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Volumes": null, + "ShutdownDelay": null, + "StopAfterClientDisconnect": null, + "MaxClientDisconnect": null + } + ], + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "", + "MinHealthyTime": 0, + "HealthyDeadline": 0, + "ProgressDeadline": 0, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Multiregion": null, + "Periodic": null, + "ParameterizedJob": null, + "Dispatched": false, + "DispatchIdempotencyToken": "", + "Payload": null, + "Meta": null, + "ConsulToken": "", + "ConsulNamespace": "", + "VaultToken": "", + "VaultNamespace": "", + "NomadTokenID": "", + "Status": "running", + "StatusDescription": "", + "Stable": true, + "Version": 1, + "SubmitTime": 1705752438438572000, + "CreateIndex": 11221, + "ModifyIndex": 11238, + "JobModifyIndex": 11232 +} \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/job_job8_UDP.json b/pkg/provider/nomad/fixtures/job_job8_UDP.json new file mode 100644 index 000000000..eda3df6f0 --- /dev/null +++ b/pkg/provider/nomad/fixtures/job_job8_UDP.json @@ -0,0 +1,221 @@ +{ + "Stop": false, + "Region": "global", + "Namespace": "default", + "ID": "job8", + "ParentID": "", + "Name": "job8", + "Type": "service", + "Priority": 50, + "AllAtOnce": false, + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Constraints": null, + "Affinities": null, + "Spreads": null, + "TaskGroups": [ + { + "Name": "group1", + "Count": 1, + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000, + "ProgressDeadline": 600000000000, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Migrate": { + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000 + }, + "Constraints": [ + { + "LTarget": "${attr.nomad.service_discovery}", + "RTarget": "true", + "Operand": "=" + } + ], + "Scaling": { + "ID": "3828aaa9-4e16-cde5-cc80-f0cec4a7c82d", + "Type": "horizontal", + "Target": { + "Namespace": "default", + "Job": "job8", + "Group": "group1" + }, + "Policy": null, + "Min": 0, + "Max": 3, + "Enabled": true, + "CreateIndex": 0, + "ModifyIndex": 0 + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "Tasks": [ + { + "Name": "task1", + "Driver": "docker", + "User": "", + "Config": { + "ports": [ + "http" + ], + "image": "nginx" + }, + "Env": null, + "Services": null, + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + } + ], + "EphemeralDisk": { + "Sticky": false, + "SizeMB": 300, + "Migrate": false + }, + "Meta": null, + "ReschedulePolicy": { + "Attempts": 0, + "Interval": 0, + "Delay": 30000000000, + "DelayFunction": "exponential", + "MaxDelay": 3600000000000, + "Unlimited": true + }, + "Affinities": null, + "Spreads": null, + "Networks": [ + { + "Mode": "", + "Device": "", + "CIDR": "", + "IP": "", + "Hostname": "", + "MBits": 0, + "DNS": null, + "ReservedPorts": null, + "DynamicPorts": [ + { + "Label": "http", + "Value": 0, + "To": 80, + "HostNetwork": "default" + } + ] + } + ], + "Consul": { + "Namespace": "" + }, + "Services": [ + { + "Name": "job8", + "TaskName": "", + "PortLabel": "http", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true", + "traefik.udp.routers.job8.service=job8" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Volumes": null, + "ShutdownDelay": null, + "StopAfterClientDisconnect": null, + "MaxClientDisconnect": null + } + ], + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "", + "MinHealthyTime": 0, + "HealthyDeadline": 0, + "ProgressDeadline": 0, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Multiregion": null, + "Periodic": null, + "ParameterizedJob": null, + "Dispatched": false, + "DispatchIdempotencyToken": "", + "Payload": null, + "Meta": null, + "ConsulToken": "", + "ConsulNamespace": "", + "VaultToken": "", + "VaultNamespace": "", + "NomadTokenID": "", + "Status": "running", + "StatusDescription": "", + "Stable": true, + "Version": 2, + "SubmitTime": 1705753285493029600, + "CreateIndex": 11276, + "ModifyIndex": 11303, + "JobModifyIndex": 11297 +} \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/job_job9_ScalingEnabled_Stopped.json b/pkg/provider/nomad/fixtures/job_job9_ScalingEnabled_Stopped.json new file mode 100644 index 000000000..596072a67 --- /dev/null +++ b/pkg/provider/nomad/fixtures/job_job9_ScalingEnabled_Stopped.json @@ -0,0 +1,220 @@ +{ + "Stop": true, + "Region": "global", + "Namespace": "default", + "ID": "job9", + "ParentID": "", + "Name": "job9", + "Type": "service", + "Priority": 50, + "AllAtOnce": false, + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Constraints": null, + "Affinities": null, + "Spreads": null, + "TaskGroups": [ + { + "Name": "group1", + "Count": 1, + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000, + "ProgressDeadline": 600000000000, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Migrate": { + "MaxParallel": 1, + "HealthCheck": "checks", + "MinHealthyTime": 10000000000, + "HealthyDeadline": 300000000000 + }, + "Constraints": [ + { + "LTarget": "${attr.nomad.service_discovery}", + "RTarget": "true", + "Operand": "=" + } + ], + "Scaling": { + "ID": "094d38a7-24cf-d3a4-3a82-cfc50ef7c597", + "Type": "horizontal", + "Target": { + "Group": "group1", + "Namespace": "default", + "Job": "job9" + }, + "Policy": null, + "Min": 0, + "Max": 3, + "Enabled": true, + "CreateIndex": 0, + "ModifyIndex": 0 + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "Tasks": [ + { + "Name": "task1", + "Driver": "docker", + "User": "", + "Config": { + "image": "nginx", + "ports": [ + "http" + ] + }, + "Env": null, + "Services": null, + "Vault": null, + "Templates": null, + "Constraints": null, + "Affinities": null, + "Resources": { + "CPU": 100, + "Cores": 0, + "MemoryMB": 300, + "MemoryMaxMB": 0, + "DiskMB": 0, + "IOPS": 0, + "Networks": null, + "Devices": null + }, + "RestartPolicy": { + "Attempts": 2, + "Interval": 1800000000000, + "Delay": 15000000000, + "Mode": "fail", + "RenderTemplates": false + }, + "DispatchPayload": null, + "Lifecycle": null, + "Meta": null, + "KillTimeout": 5000000000, + "LogConfig": { + "MaxFiles": 10, + "MaxFileSizeMB": 10, + "Disabled": false + }, + "Artifacts": null, + "Leader": false, + "ShutdownDelay": 0, + "VolumeMounts": null, + "ScalingPolicies": null, + "KillSignal": "", + "Kind": "", + "CSIPluginConfig": null, + "Identity": null + } + ], + "EphemeralDisk": { + "Sticky": false, + "SizeMB": 300, + "Migrate": false + }, + "Meta": null, + "ReschedulePolicy": { + "Attempts": 0, + "Interval": 0, + "Delay": 30000000000, + "DelayFunction": "exponential", + "MaxDelay": 3600000000000, + "Unlimited": true + }, + "Affinities": null, + "Spreads": null, + "Networks": [ + { + "Mode": "", + "Device": "", + "CIDR": "", + "IP": "", + "Hostname": "", + "MBits": 0, + "DNS": null, + "ReservedPorts": null, + "DynamicPorts": [ + { + "Label": "http", + "Value": 0, + "To": 80, + "HostNetwork": "default" + } + ] + } + ], + "Consul": { + "Namespace": "" + }, + "Services": [ + { + "Name": "job9", + "TaskName": "", + "PortLabel": "http", + "AddressMode": "auto", + "Address": "", + "EnableTagOverride": false, + "Tags": [ + "traefik.enable=true" + ], + "CanaryTags": null, + "Checks": null, + "Connect": null, + "Meta": null, + "CanaryMeta": null, + "TaggedAddresses": null, + "Namespace": "default", + "OnUpdate": "require_healthy", + "Provider": "nomad" + } + ], + "Volumes": null, + "ShutdownDelay": null, + "StopAfterClientDisconnect": null, + "MaxClientDisconnect": null + } + ], + "Update": { + "Stagger": 30000000000, + "MaxParallel": 1, + "HealthCheck": "", + "MinHealthyTime": 0, + "HealthyDeadline": 0, + "ProgressDeadline": 0, + "AutoRevert": false, + "AutoPromote": false, + "Canary": 0 + }, + "Multiregion": null, + "Periodic": null, + "ParameterizedJob": null, + "Dispatched": false, + "DispatchIdempotencyToken": "", + "Payload": null, + "Meta": null, + "ConsulToken": "", + "ConsulNamespace": "", + "VaultToken": "", + "VaultNamespace": "", + "NomadTokenID": "", + "Status": "dead", + "StatusDescription": "", + "Stable": true, + "Version": 3, + "SubmitTime": 1705753979676559600, + "CreateIndex": 11317, + "ModifyIndex": 11346, + "JobModifyIndex": 11344 +} \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/jobs_job1.json b/pkg/provider/nomad/fixtures/jobs_job1.json new file mode 100644 index 000000000..66016a6a9 --- /dev/null +++ b/pkg/provider/nomad/fixtures/jobs_job1.json @@ -0,0 +1,56 @@ +[ + { + "ID": "job1", + "ParentID": "", + "Name": "job1", + "Namespace": "default", + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Multiregion": null, + "Type": "service", + "Priority": 50, + "Periodic": false, + "ParameterizedJob": false, + "Stop": false, + "Status": "running", + "StatusDescription": "", + "JobSummary": { + "JobID": "job1", + "Namespace": "default", + "Summary": { + "job1": { + "Queued": 0, + "Complete": 1, + "Failed": 0, + "Running": 0, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + }, + "group1": { + "Queued": 0, + "Complete": 6, + "Failed": 1, + "Running": 1, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + } + }, + "Children": { + "Pending": 0, + "Running": 0, + "Dead": 0 + }, + "CreateIndex": 493, + "ModifyIndex": 9909 + }, + "CreateIndex": 493, + "ModifyIndex": 9961, + "JobModifyIndex": 9955, + "SubmitTime": 1705690395733241600, + "Meta": null + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/jobs_job2.json b/pkg/provider/nomad/fixtures/jobs_job2.json new file mode 100644 index 000000000..ee817aa0a --- /dev/null +++ b/pkg/provider/nomad/fixtures/jobs_job2.json @@ -0,0 +1,47 @@ +[ + { + "ID": "job2", + "ParentID": "", + "Name": "job2", + "Namespace": "default", + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Multiregion": null, + "Type": "service", + "Priority": 50, + "Periodic": false, + "ParameterizedJob": false, + "Stop": false, + "Status": "dead", + "StatusDescription": "", + "JobSummary": { + "JobID": "job2", + "Namespace": "default", + "Summary": { + "group1": { + "Queued": 0, + "Complete": 6, + "Failed": 6, + "Running": 0, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + } + }, + "Children": { + "Pending": 0, + "Running": 0, + "Dead": 0 + }, + "CreateIndex": 2923, + "ModifyIndex": 10051 + }, + "CreateIndex": 2923, + "ModifyIndex": 10048, + "JobModifyIndex": 10044, + "SubmitTime": 1705690880440177400, + "Meta": null + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/jobs_job3.json b/pkg/provider/nomad/fixtures/jobs_job3.json new file mode 100644 index 000000000..bf69bc069 --- /dev/null +++ b/pkg/provider/nomad/fixtures/jobs_job3.json @@ -0,0 +1,47 @@ +[ + { + "ID": "job3", + "ParentID": "", + "Name": "job3", + "Namespace": "default", + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Multiregion": null, + "Type": "service", + "Priority": 50, + "Periodic": false, + "ParameterizedJob": false, + "Stop": false, + "Status": "running", + "StatusDescription": "", + "JobSummary": { + "JobID": "job3", + "Namespace": "default", + "Summary": { + "group1": { + "Queued": 0, + "Complete": 1, + "Failed": 0, + "Running": 1, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + } + }, + "Children": { + "Pending": 0, + "Running": 0, + "Dead": 0 + }, + "CreateIndex": 2932, + "ModifyIndex": 9989 + }, + "CreateIndex": 2932, + "ModifyIndex": 9992, + "JobModifyIndex": 9983, + "SubmitTime": 1705690892484556300, + "Meta": null + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/jobs_job4.json b/pkg/provider/nomad/fixtures/jobs_job4.json new file mode 100644 index 000000000..dfd0f3a11 --- /dev/null +++ b/pkg/provider/nomad/fixtures/jobs_job4.json @@ -0,0 +1,47 @@ +[ + { + "ID": "job4", + "ParentID": "", + "Name": "job4", + "Namespace": "default", + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Multiregion": null, + "Type": "service", + "Priority": 50, + "Periodic": false, + "ParameterizedJob": false, + "Stop": true, + "Status": "dead", + "StatusDescription": "", + "JobSummary": { + "JobID": "job4", + "Namespace": "default", + "Summary": { + "group1": { + "Queued": 0, + "Complete": 6, + "Failed": 0, + "Running": 0, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + } + }, + "Children": { + "Pending": 0, + "Running": 0, + "Dead": 0 + }, + "CreateIndex": 3079, + "ModifyIndex": 10057 + }, + "CreateIndex": 3079, + "ModifyIndex": 10054, + "JobModifyIndex": 10052, + "SubmitTime": 1705690905317339000, + "Meta": null + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/jobs_job5.json b/pkg/provider/nomad/fixtures/jobs_job5.json new file mode 100644 index 000000000..bf846ed21 --- /dev/null +++ b/pkg/provider/nomad/fixtures/jobs_job5.json @@ -0,0 +1,56 @@ +[ + { + "ID": "job5", + "ParentID": "", + "Name": "job5", + "Namespace": "default", + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Multiregion": null, + "Type": "service", + "Priority": 50, + "Periodic": false, + "ParameterizedJob": false, + "Stop": false, + "Status": "running", + "StatusDescription": "", + "JobSummary": { + "JobID": "job5", + "Namespace": "default", + "Summary": { + "job5": { + "Queued": 0, + "Complete": 2, + "Failed": 0, + "Running": 0, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + }, + "group1": { + "Queued": 0, + "Complete": 4, + "Failed": 0, + "Running": 1, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + } + }, + "Children": { + "Pending": 0, + "Running": 0, + "Dead": 0 + }, + "CreateIndex": 3095, + "ModifyIndex": 10015 + }, + "CreateIndex": 3095, + "ModifyIndex": 10018, + "JobModifyIndex": 10008, + "SubmitTime": 1705690918813110300, + "Meta": null + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/jobs_job6.json b/pkg/provider/nomad/fixtures/jobs_job6.json new file mode 100644 index 000000000..e4c34e238 --- /dev/null +++ b/pkg/provider/nomad/fixtures/jobs_job6.json @@ -0,0 +1,47 @@ +[ + { + "ID": "job6", + "ParentID": "", + "Name": "job6", + "Namespace": "default", + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Multiregion": null, + "Type": "service", + "Priority": 50, + "Periodic": false, + "ParameterizedJob": false, + "Stop": false, + "Status": "dead", + "StatusDescription": "", + "JobSummary": { + "JobID": "job6", + "Namespace": "default", + "Summary": { + "group1": { + "Queued": 0, + "Complete": 3, + "Failed": 0, + "Running": 0, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + } + }, + "Children": { + "Pending": 0, + "Running": 0, + "Dead": 0 + }, + "CreateIndex": 9941, + "ModifyIndex": 10082 + }, + "CreateIndex": 9941, + "ModifyIndex": 10078, + "JobModifyIndex": 10074, + "SubmitTime": 1705690931854150700, + "Meta": null + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/jobs_job7.json b/pkg/provider/nomad/fixtures/jobs_job7.json new file mode 100644 index 000000000..f3de421e2 --- /dev/null +++ b/pkg/provider/nomad/fixtures/jobs_job7.json @@ -0,0 +1,47 @@ +[ + { + "ID": "job7", + "ParentID": "", + "Name": "job7", + "Namespace": "default", + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Multiregion": null, + "Type": "service", + "Priority": 50, + "Periodic": false, + "ParameterizedJob": false, + "Stop": false, + "Status": "running", + "StatusDescription": "", + "JobSummary": { + "JobID": "job7", + "Namespace": "default", + "Summary": { + "group1": { + "Queued": 0, + "Complete": 0, + "Failed": 0, + "Running": 1, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + } + }, + "Children": { + "Pending": 0, + "Running": 0, + "Dead": 0 + }, + "CreateIndex": 11221, + "ModifyIndex": 11225 + }, + "CreateIndex": 11221, + "ModifyIndex": 11238, + "JobModifyIndex": 11232, + "SubmitTime": 1705752438438572000, + "Meta": null + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/jobs_job8.json b/pkg/provider/nomad/fixtures/jobs_job8.json new file mode 100644 index 000000000..e77e2c718 --- /dev/null +++ b/pkg/provider/nomad/fixtures/jobs_job8.json @@ -0,0 +1,47 @@ +[ + { + "ID": "job8", + "ParentID": "", + "Name": "job8", + "Namespace": "default", + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Multiregion": null, + "Type": "service", + "Priority": 50, + "Periodic": false, + "ParameterizedJob": false, + "Stop": false, + "Status": "running", + "StatusDescription": "", + "JobSummary": { + "JobID": "job8", + "Namespace": "default", + "Summary": { + "group1": { + "Queued": 0, + "Complete": 0, + "Failed": 0, + "Running": 1, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + } + }, + "Children": { + "Pending": 0, + "Running": 0, + "Dead": 0 + }, + "CreateIndex": 11276, + "ModifyIndex": 11281 + }, + "CreateIndex": 11276, + "ModifyIndex": 11303, + "JobModifyIndex": 11297, + "SubmitTime": 1705753285493029600, + "Meta": null + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/jobs_job9.json b/pkg/provider/nomad/fixtures/jobs_job9.json new file mode 100644 index 000000000..2f45e4ae7 --- /dev/null +++ b/pkg/provider/nomad/fixtures/jobs_job9.json @@ -0,0 +1,47 @@ +[ + { + "ID": "job9", + "ParentID": "", + "Name": "job9", + "Namespace": "default", + "Datacenters": [ + "dc1" + ], + "NodePool": "default", + "Multiregion": null, + "Type": "service", + "Priority": 50, + "Periodic": false, + "ParameterizedJob": false, + "Stop": true, + "Status": "dead", + "StatusDescription": "", + "JobSummary": { + "JobID": "job9", + "Namespace": "default", + "Summary": { + "group1": { + "Queued": 0, + "Complete": 1, + "Failed": 0, + "Running": 0, + "Starting": 0, + "Lost": 0, + "Unknown": 0 + } + }, + "Children": { + "Pending": 0, + "Running": 0, + "Dead": 0 + }, + "CreateIndex": 11317, + "ModifyIndex": 11349 + }, + "CreateIndex": 11317, + "ModifyIndex": 11346, + "JobModifyIndex": 11344, + "SubmitTime": 1705753979676559600, + "Meta": null + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_hello.json b/pkg/provider/nomad/fixtures/service_hello.json new file mode 100644 index 000000000..e4f675b16 --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_hello.json @@ -0,0 +1,20 @@ +[ + { + "Address": "127.0.0.1", + "AllocID": "71a63a80-a98a-93ee-4fd7-73b808577c20", + "CreateIndex": 18, + "Datacenter": "dc1", + "ID": "_nomad-task-71a63a80-a98a-93ee-4fd7-73b808577c20-group-hello-nomad-hello-nomad-http", + "JobID": "echo", + "ModifyIndex": 18, + "Namespace": "default", + "NodeID": "6d7f412e-e7ff-2e66-d47b-867b0e9d8726", + "Port": 20627, + "ServiceName": "hello-nomad", + "Tags": [ + "traefik.enable=true", + "traefik.http.routers.hellon.entrypoints=web", + "traefik.http.routers.hellon.service=hello-nomad" + ] + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_job1.json b/pkg/provider/nomad/fixtures/service_job1.json new file mode 100644 index 000000000..18a6c2ba0 --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job1.json @@ -0,0 +1,18 @@ +[ + { + "ID": "_nomad-task-03c7270c-f475-5981-1932-87c0a8a5aa24-group-group1-job1-http", + "ServiceName": "job1", + "Namespace": "default", + "NodeID": "e262ecb6-a7ac-7c3e-4de0-06445559e596", + "Datacenter": "dc1", + "JobID": "job1", + "AllocID": "03c7270c-f475-5981-1932-87c0a8a5aa24", + "Tags": [ + "traefik.enable=true" + ], + "Address": "192.168.1.21", + "Port": 29916, + "CreateIndex": 5753, + "ModifyIndex": 9958 + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_job2.json b/pkg/provider/nomad/fixtures/service_job2.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job2.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_job3.json b/pkg/provider/nomad/fixtures/service_job3.json new file mode 100644 index 000000000..ece0f4235 --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job3.json @@ -0,0 +1,18 @@ +[ + { + "ID": "_nomad-task-dd945b55-70fa-0efc-6512-b88fb55ce33f-group-group1-job3-http", + "ServiceName": "job3", + "Namespace": "default", + "NodeID": "e262ecb6-a7ac-7c3e-4de0-06445559e596", + "Datacenter": "dc1", + "JobID": "job3", + "AllocID": "dd945b55-70fa-0efc-6512-b88fb55ce33f", + "Tags": [ + "traefik.enable=true" + ], + "Address": "192.168.1.21", + "Port": 29983, + "CreateIndex": 9987, + "ModifyIndex": 9987 + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_job4.json b/pkg/provider/nomad/fixtures/service_job4.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job4.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_job5task1.json b/pkg/provider/nomad/fixtures/service_job5task1.json new file mode 100644 index 000000000..a438e607f --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job5task1.json @@ -0,0 +1,18 @@ +[ + { + "ID": "_nomad-task-a98bac3d-5382-3032-1954-57aff58b20c1-task1-job5task1-http", + "ServiceName": "job5task1", + "Namespace": "default", + "NodeID": "e262ecb6-a7ac-7c3e-4de0-06445559e596", + "Datacenter": "dc1", + "JobID": "job5", + "AllocID": "a98bac3d-5382-3032-1954-57aff58b20c1", + "Tags": [ + "traefik.enable=true" + ], + "Address": "192.168.1.21", + "Port": 24542, + "CreateIndex": 10013, + "ModifyIndex": 10013 + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_job5task2.json b/pkg/provider/nomad/fixtures/service_job5task2.json new file mode 100644 index 000000000..a9d32113f --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job5task2.json @@ -0,0 +1,18 @@ +[ + { + "ID": "_nomad-task-a98bac3d-5382-3032-1954-57aff58b20c1-task2-job5task2-other", + "ServiceName": "job5task2", + "Namespace": "default", + "NodeID": "e262ecb6-a7ac-7c3e-4de0-06445559e596", + "Datacenter": "dc1", + "JobID": "job5", + "AllocID": "a98bac3d-5382-3032-1954-57aff58b20c1", + "Tags": [ + "traefik.enable=true" + ], + "Address": "192.168.1.21", + "Port": 30165, + "CreateIndex": 10014, + "ModifyIndex": 10014 + } +] diff --git a/pkg/provider/nomad/fixtures/service_job6task1.json b/pkg/provider/nomad/fixtures/service_job6task1.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job6task1.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_job6task2.json b/pkg/provider/nomad/fixtures/service_job6task2.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job6task2.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_job7.json b/pkg/provider/nomad/fixtures/service_job7.json new file mode 100644 index 000000000..35ab344cf --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job7.json @@ -0,0 +1,20 @@ +[ + { + "ID": "_nomad-task-3e1cc853-f6b1-2c46-6f20-332a6e91794b-group-group1-job7-http", + "ServiceName": "job7", + "Namespace": "default", + "NodeID": "e262ecb6-a7ac-7c3e-4de0-06445559e596", + "Datacenter": "dc1", + "JobID": "job7", + "AllocID": "3e1cc853-f6b1-2c46-6f20-332a6e91794b", + "Tags": [ + "traefik.enable=true", + "traefik.tcp.routers.job7.rule=HostSNI(`job7`)", + "traefik.tcp.routers.job7.tls.passthrough=true" + ], + "Address": "192.168.1.21", + "Port": 22899, + "CreateIndex": 11224, + "ModifyIndex": 11271 + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_job8.json b/pkg/provider/nomad/fixtures/service_job8.json new file mode 100644 index 000000000..d670c568a --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job8.json @@ -0,0 +1,19 @@ +[ + { + "ID": "_nomad-task-fffd3d81-66ac-ed6d-d9a1-fc41b2a26d07-group-group1-job8-http", + "ServiceName": "job8", + "Namespace": "default", + "NodeID": "e262ecb6-a7ac-7c3e-4de0-06445559e596", + "Datacenter": "dc1", + "JobID": "job8", + "AllocID": "fffd3d81-66ac-ed6d-d9a1-fc41b2a26d07", + "Tags": [ + "traefik.enable=true", + "traefik.udp.routers.job8.service=job8" + ], + "Address": "192.168.1.21", + "Port": 24268, + "CreateIndex": 11279, + "ModifyIndex": 11300 + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_job9.json b/pkg/provider/nomad/fixtures/service_job9.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_job9.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/service_redis.json b/pkg/provider/nomad/fixtures/service_redis.json new file mode 100644 index 000000000..b1f4e752e --- /dev/null +++ b/pkg/provider/nomad/fixtures/service_redis.json @@ -0,0 +1,18 @@ +[ + { + "Address": "127.0.0.1", + "AllocID": "07501480-8175-8071-7da6-133bd1ff890f", + "CreateIndex": 46, + "Datacenter": "dc1", + "ID": "_nomad-task-07501480-8175-8071-7da6-133bd1ff890f-group-redis-redis-redis", + "JobID": "echo", + "ModifyIndex": 46, + "Namespace": "default", + "NodeID": "6d7f412e-e7ff-2e66-d47b-867b0e9d8726", + "Port": 30826, + "ServiceName": "redis", + "Tags": [ + "traefik.enable=true" + ] + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/fixtures/services.json b/pkg/provider/nomad/fixtures/services.json new file mode 100644 index 000000000..dfe303183 --- /dev/null +++ b/pkg/provider/nomad/fixtures/services.json @@ -0,0 +1,21 @@ +[ + { + "Namespace": "default", + "Services": [ + { + "ServiceName": "redis", + "Tags": [ + "traefik.enable=true" + ] + }, + { + "ServiceName": "hello-nomad", + "Tags": [ + "traefik.enable=true", + "traefik.http.routers.hellon.entrypoints=web", + "traefik.http.routers.hellon.service=hello-nomad" + ] + } + ] + } +] \ No newline at end of file diff --git a/pkg/provider/nomad/nomad.go b/pkg/provider/nomad/nomad.go index b836707eb..5a595dca6 100644 --- a/pkg/provider/nomad/nomad.go +++ b/pkg/provider/nomad/nomad.go @@ -85,13 +85,14 @@ func (p *ProviderBuilder) BuildProviders() []*Provider { // Configuration represents the Nomad provider configuration. type Configuration struct { - DefaultRule string `description:"Default rule." json:"defaultRule,omitempty" toml:"defaultRule,omitempty" yaml:"defaultRule,omitempty"` - Constraints string `description:"Constraints is an expression that Traefik matches against the Nomad service's tags to determine whether to create route(s) for that service." json:"constraints,omitempty" toml:"constraints,omitempty" yaml:"constraints,omitempty" export:"true"` - Endpoint *EndpointConfig `description:"Nomad endpoint settings" json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty" export:"true"` - Prefix string `description:"Prefix for nomad service tags." json:"prefix,omitempty" toml:"prefix,omitempty" yaml:"prefix,omitempty" export:"true"` - Stale bool `description:"Use stale consistency for catalog reads." json:"stale,omitempty" toml:"stale,omitempty" yaml:"stale,omitempty" export:"true"` - ExposedByDefault bool `description:"Expose Nomad services by default." json:"exposedByDefault,omitempty" toml:"exposedByDefault,omitempty" yaml:"exposedByDefault,omitempty" export:"true"` - RefreshInterval ptypes.Duration `description:"Interval for polling Nomad API." json:"refreshInterval,omitempty" toml:"refreshInterval,omitempty" yaml:"refreshInterval,omitempty" export:"true"` + DefaultRule string `description:"Default rule." json:"defaultRule,omitempty" toml:"defaultRule,omitempty" yaml:"defaultRule,omitempty"` + Constraints string `description:"Constraints is an expression that Traefik matches against the Nomad service's tags to determine whether to create route(s) for that service." json:"constraints,omitempty" toml:"constraints,omitempty" yaml:"constraints,omitempty" export:"true"` + Endpoint *EndpointConfig `description:"Nomad endpoint settings" json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty" export:"true"` + Prefix string `description:"Prefix for nomad service tags." json:"prefix,omitempty" toml:"prefix,omitempty" yaml:"prefix,omitempty" export:"true"` + Stale bool `description:"Use stale consistency for catalog reads." json:"stale,omitempty" toml:"stale,omitempty" yaml:"stale,omitempty" export:"true"` + ExposedByDefault bool `description:"Expose Nomad services by default." json:"exposedByDefault,omitempty" toml:"exposedByDefault,omitempty" yaml:"exposedByDefault,omitempty" export:"true"` + RefreshInterval ptypes.Duration `description:"Interval for polling Nomad API." json:"refreshInterval,omitempty" toml:"refreshInterval,omitempty" yaml:"refreshInterval,omitempty" export:"true"` + AllowEmptyServices bool `description:"Allow the creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"` } // SetDefaults sets the default values for the Nomad Traefik Provider Configuration. @@ -116,6 +117,7 @@ func (c *Configuration) SetDefaults() { c.ExposedByDefault = true c.RefreshInterval = ptypes.Duration(15 * time.Second) c.DefaultRule = defaultTemplateRule + c.AllowEmptyServices = false } type EndpointConfig struct { @@ -222,10 +224,20 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. } func (p *Provider) loadConfiguration(ctx context.Context, configurationC chan<- dynamic.Message) error { - items, err := p.getNomadServiceData(ctx) - if err != nil { - return err + var items []item + var err error + if p.AllowEmptyServices { + items, err = p.getNomadServiceDataWithEmptyServices(ctx) + if err != nil { + return err + } + } else { + items, err = p.getNomadServiceData(ctx) + if err != nil { + return err + } } + configurationC <- dynamic.Message{ ProviderName: p.name, Configuration: p.buildConfig(ctx, items), @@ -291,6 +303,98 @@ func (p *Provider) getNomadServiceData(ctx context.Context) ([]item, error) { return items, nil } +func (p *Provider) getNomadServiceDataWithEmptyServices(ctx context.Context) ([]item, error) { + jobsOpts := &api.QueryOptions{AllowStale: p.Stale} + jobsOpts = jobsOpts.WithContext(ctx) + + jobStubs, _, err := p.client.Jobs().List(jobsOpts) + if err != nil { + return nil, err + } + + var items []item + + // Get Services even when they are scaled down to zero. Currently the nomad service interface does not support this. https://github.com/hashicorp/nomad/issues/19731 + for _, jobStub := range jobStubs { + jobInfoOpts := &api.QueryOptions{} + jobInfoOpts = jobInfoOpts.WithContext(ctx) + + job, _, err := p.client.Jobs().Info(jobStub.ID, jobInfoOpts) + if err != nil { + return nil, err + } + + for _, taskGroup := range job.TaskGroups { + services := []*api.Service{} + // Get all services in job -> taskgroup + services = append(services, taskGroup.Services...) + + // Get all services in job -> taskgroup -> tasks + for _, task := range taskGroup.Tasks { + services = append(services, task.Services...) + } + + for _, service := range services { + logger := log.Ctx(ctx).With().Str("serviceName", service.TaskName).Logger() + + extraConf := p.getExtraConf(service.Tags) + if !extraConf.Enable { + logger.Debug().Msg("Filter Nomad service that is not enabled") + continue + } + + matches, err := constraints.MatchTags(service.Tags, p.Constraints) + if err != nil { + logger.Error().Err(err).Msg("Error matching constraint expressions") + continue + } + + if !matches { + logger.Debug().Msgf("Filter Nomad service not matching constraints: %q", p.Constraints) + continue + } + + if nil != taskGroup.Scaling && *taskGroup.Scaling.Enabled && *taskGroup.Count == 0 { + // Add items without address + items = append(items, item{ + // Create a unique id for non registered services + ID: fmt.Sprintf("%s-%s-%s-%s-%s", *job.Namespace, *job.Name, *taskGroup.Name, service.TaskName, service.Name), + Name: service.Name, + Namespace: *job.Namespace, + Node: "", + Datacenter: "", + Address: "", + Port: -1, + Tags: service.Tags, + ExtraConf: p.getExtraConf(service.Tags), + }) + } else { + instances, err := p.fetchService(ctx, service.Name) + if err != nil { + return nil, err + } + + for _, i := range instances { + items = append(items, item{ + ID: i.ID, + Name: i.ServiceName, + Namespace: i.Namespace, + Node: i.NodeID, + Datacenter: i.Datacenter, + Address: i.Address, + Port: i.Port, + Tags: i.Tags, + ExtraConf: p.getExtraConf(i.Tags), + }) + } + } + } + } + } + + return items, nil +} + // getExtraConf returns a configuration with settings which are not part of the dynamic configuration (e.g. ".enable"). func (p *Provider) getExtraConf(tags []string) configuration { labels := tagsToLabels(tags, p.Prefix) diff --git a/pkg/provider/nomad/nomad_test.go b/pkg/provider/nomad/nomad_test.go index ffbb3f89d..2794fab0c 100644 --- a/pkg/provider/nomad/nomad_test.go +++ b/pkg/provider/nomad/nomad_test.go @@ -2,8 +2,11 @@ package nomad import ( "context" + "fmt" "net/http" "net/http/httptest" + "os" + "path/filepath" "strings" "testing" @@ -12,6 +15,17 @@ import ( "github.com/traefik/traefik/v3/pkg/types" ) +var responses = map[string][]byte{} + +func TestMain(m *testing.M) { + err := setup() + if err != nil { + fmt.Fprintf(os.Stderr, "%s", err.Error()) + os.Exit(1) + } + m.Run() +} + func Test_globalConfig(t *testing.T) { cases := []struct { Name string @@ -131,15 +145,311 @@ func TestProvider_SetDefaults_Endpoint(t *testing.T) { } } +func Test_getNomadServiceDataWithEmptyServices_GroupService_Scaling1(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasSuffix(r.RequestURI, "/v1/jobs"): + _, _ = w.Write(responses["jobs_job1"]) + case strings.HasSuffix(r.RequestURI, "/v1/job/job1"): + _, _ = w.Write(responses["job_job1_WithGroupService_Scaling1"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job1"): + _, _ = w.Write(responses["service_job1"]) + } + })) + + t.Cleanup(ts.Close) + + p := new(Provider) + p.SetDefaults() + p.Endpoint.Address = ts.URL + err := p.Init() + require.NoError(t, err) + + // fudge client, avoid starting up via Provide + p.client, err = createClient(p.namespace, p.Endpoint) + require.NoError(t, err) + + // make the query for services + items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + require.NoError(t, err) + require.Len(t, items, 1) +} + +func Test_getNomadServiceDataWithEmptyServices_GroupService_Scaling0(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasSuffix(r.RequestURI, "/v1/jobs"): + _, _ = w.Write(responses["jobs_job2"]) + case strings.HasSuffix(r.RequestURI, "/v1/job/job2"): + _, _ = w.Write(responses["job_job2_WithGroupService_Scaling0"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job2"): + _, _ = w.Write(responses["service_job2"]) + } + })) + + t.Cleanup(ts.Close) + + p := new(Provider) + p.SetDefaults() + p.Endpoint.Address = ts.URL + err := p.Init() + require.NoError(t, err) + + // fudge client, avoid starting up via Provide + p.client, err = createClient(p.namespace, p.Endpoint) + require.NoError(t, err) + + // make the query for services + items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + require.NoError(t, err) + require.Len(t, items, 1) +} + +func Test_getNomadServiceDataWithEmptyServices_GroupService_ScalingDisabled(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasSuffix(r.RequestURI, "/v1/jobs"): + _, _ = w.Write(responses["jobs_job3"]) + case strings.HasSuffix(r.RequestURI, "/v1/job/job3"): + _, _ = w.Write(responses["job_job3_WithGroupService_ScalingDisabled"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job3"): + _, _ = w.Write(responses["service_job3"]) + } + })) + + t.Cleanup(ts.Close) + + p := new(Provider) + p.SetDefaults() + p.Endpoint.Address = ts.URL + err := p.Init() + require.NoError(t, err) + + // fudge client, avoid starting up via Provide + p.client, err = createClient(p.namespace, p.Endpoint) + require.NoError(t, err) + + // make the query for services + items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + require.NoError(t, err) + require.Len(t, items, 1) +} + +func Test_getNomadServiceDataWithEmptyServices_GroupService_ScalingDisabled_Stopped(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasSuffix(r.RequestURI, "/v1/jobs"): + _, _ = w.Write(responses["jobs_job4"]) + case strings.HasSuffix(r.RequestURI, "/v1/job/job4"): + _, _ = w.Write(responses["job_job4_WithGroupService_ScalingDisabled_Stopped"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job4"): + _, _ = w.Write(responses["service_job4"]) + } + })) + + t.Cleanup(ts.Close) + + p := new(Provider) + p.SetDefaults() + p.Endpoint.Address = ts.URL + err := p.Init() + require.NoError(t, err) + + // fudge client, avoid starting up via Provide + p.client, err = createClient(p.namespace, p.Endpoint) + require.NoError(t, err) + + // make the query for services + items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + require.NoError(t, err) + + // Should not be listed as job is stopped + require.Empty(t, items) +} + +func Test_getNomadServiceDataWithEmptyServices_GroupTaskService_Scaling1(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasSuffix(r.RequestURI, "/v1/jobs"): + _, _ = w.Write(responses["jobs_job5"]) + case strings.HasSuffix(r.RequestURI, "/v1/job/job5"): + _, _ = w.Write(responses["job_job5_WithGroupTaskService_Scaling1"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job5task1"): + _, _ = w.Write(responses["service_job5task1"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job5task2"): + _, _ = w.Write(responses["service_job5task2"]) + } + })) + + t.Cleanup(ts.Close) + + p := new(Provider) + p.SetDefaults() + p.Endpoint.Address = ts.URL + err := p.Init() + require.NoError(t, err) + + // fudge client, avoid starting up via Provide + p.client, err = createClient(p.namespace, p.Endpoint) + require.NoError(t, err) + + // make the query for services + items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + require.NoError(t, err) + require.Len(t, items, 2) +} + +func Test_getNomadServiceDataWithEmptyServices_GroupTaskService_Scaling0(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasSuffix(r.RequestURI, "/v1/jobs"): + _, _ = w.Write(responses["jobs_job6"]) + case strings.HasSuffix(r.RequestURI, "/v1/job/job6"): + _, _ = w.Write(responses["job_job6_WithGroupTaskService_Scaling0"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job6task1"): + _, _ = w.Write(responses["service_job6task1"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job6task2"): + _, _ = w.Write(responses["service_job6task2"]) + } + })) + + t.Cleanup(ts.Close) + + p := new(Provider) + p.SetDefaults() + p.Endpoint.Address = ts.URL + err := p.Init() + require.NoError(t, err) + + // fudge client, avoid starting up via Provide + p.client, err = createClient(p.namespace, p.Endpoint) + require.NoError(t, err) + + // make the query for services + items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + require.NoError(t, err) + require.Len(t, items, 2) +} + +func Test_getNomadServiceDataWithEmptyServices_TCP(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasSuffix(r.RequestURI, "/v1/jobs"): + _, _ = w.Write(responses["jobs_job7"]) + case strings.HasSuffix(r.RequestURI, "/v1/job/job7"): + _, _ = w.Write(responses["job_job7_TCP"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job7"): + _, _ = w.Write(responses["service_job7"]) + } + })) + + t.Cleanup(ts.Close) + + p := new(Provider) + p.SetDefaults() + p.Endpoint.Address = ts.URL + err := p.Init() + require.NoError(t, err) + + // fudge client, avoid starting up via Provide + p.client, err = createClient(p.namespace, p.Endpoint) + require.NoError(t, err) + + // make the query for services + items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + require.NoError(t, err) + require.Len(t, items, 1) +} + +func Test_getNomadServiceDataWithEmptyServices_UDP(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasSuffix(r.RequestURI, "/v1/jobs"): + _, _ = w.Write(responses["jobs_job8"]) + case strings.HasSuffix(r.RequestURI, "/v1/job/job8"): + _, _ = w.Write(responses["job_job8_UDP"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job8"): + _, _ = w.Write(responses["service_job8"]) + } + })) + + t.Cleanup(ts.Close) + + p := new(Provider) + p.SetDefaults() + p.Endpoint.Address = ts.URL + err := p.Init() + require.NoError(t, err) + + // fudge client, avoid starting up via Provide + p.client, err = createClient(p.namespace, p.Endpoint) + require.NoError(t, err) + + // make the query for services + items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + require.NoError(t, err) + require.Len(t, items, 1) +} + +func Test_getNomadServiceDataWithEmptyServices_ScalingEnabled_Stopped(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasSuffix(r.RequestURI, "/v1/jobs"): + _, _ = w.Write(responses["jobs_job9"]) + case strings.HasSuffix(r.RequestURI, "/v1/job/job9"): + _, _ = w.Write(responses["job_job9_ScalingEnabled_Stopped"]) + case strings.HasSuffix(r.RequestURI, "/v1/service/job9"): + _, _ = w.Write(responses["service_job9"]) + } + })) + + t.Cleanup(ts.Close) + + p := new(Provider) + p.SetDefaults() + p.Endpoint.Address = ts.URL + err := p.Init() + require.NoError(t, err) + + // fudge client, avoid starting up via Provide + p.client, err = createClient(p.namespace, p.Endpoint) + require.NoError(t, err) + + // make the query for services + items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + require.NoError(t, err) + + // Should not be listed as job is stopped + require.Empty(t, items) +} + +func setup() error { + responsesDir := "./fixtures" + files, err := os.ReadDir(responsesDir) + if err != nil { + return err + } + for _, file := range files { + if !file.IsDir() && filepath.Ext(file.Name()) == ".json" { + content, err := os.ReadFile(filepath.Join(responsesDir, file.Name())) + if err != nil { + return err + } + responses[strings.TrimSuffix(filepath.Base(file.Name()), filepath.Ext(file.Name()))] = content + } + } + return nil +} + func Test_getNomadServiceData(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch { case strings.HasSuffix(r.RequestURI, "/v1/services"): - _, _ = w.Write([]byte(services)) + _, _ = w.Write(responses["services"]) case strings.HasSuffix(r.RequestURI, "/v1/service/redis"): - _, _ = w.Write([]byte(redis)) + _, _ = w.Write(responses["service_redis"]) case strings.HasSuffix(r.RequestURI, "/v1/service/hello-nomad"): - _, _ = w.Write([]byte(hello)) + _, _ = w.Write(responses["service_hello"]) } })) t.Cleanup(ts.Close) @@ -159,71 +469,3 @@ func Test_getNomadServiceData(t *testing.T) { require.NoError(t, err) require.Len(t, items, 2) } - -const services = ` -[ - { - "Namespace": "default", - "Services": [ - { - "ServiceName": "redis", - "Tags": [ - "traefik.enable=true" - ] - }, - { - "ServiceName": "hello-nomad", - "Tags": [ - "traefik.enable=true", - "traefik.http.routers.hellon.entrypoints=web", - "traefik.http.routers.hellon.service=hello-nomad" - ] - } - ] - } -] -` - -const redis = ` -[ - { - "Address": "127.0.0.1", - "AllocID": "07501480-8175-8071-7da6-133bd1ff890f", - "CreateIndex": 46, - "Datacenter": "dc1", - "ID": "_nomad-task-07501480-8175-8071-7da6-133bd1ff890f-group-redis-redis-redis", - "JobID": "echo", - "ModifyIndex": 46, - "Namespace": "default", - "NodeID": "6d7f412e-e7ff-2e66-d47b-867b0e9d8726", - "Port": 30826, - "ServiceName": "redis", - "Tags": [ - "traefik.enable=true" - ] - } -] -` - -const hello = ` -[ - { - "Address": "127.0.0.1", - "AllocID": "71a63a80-a98a-93ee-4fd7-73b808577c20", - "CreateIndex": 18, - "Datacenter": "dc1", - "ID": "_nomad-task-71a63a80-a98a-93ee-4fd7-73b808577c20-group-hello-nomad-hello-nomad-http", - "JobID": "echo", - "ModifyIndex": 18, - "Namespace": "default", - "NodeID": "6d7f412e-e7ff-2e66-d47b-867b0e9d8726", - "Port": 20627, - "ServiceName": "hello-nomad", - "Tags": [ - "traefik.enable=true", - "traefik.http.routers.hellon.entrypoints=web", - "traefik.http.routers.hellon.service=hello-nomad" - ] - } -] -`