Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

consul: add support for canary meta #6690

Merged
merged 4 commits into from
Jan 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ IMPROVEMENTS:
* api: Added JSON representation of rules to policy endpoint response [[GH-6017](https://github.com/hashicorp/nomad/pull/6017)]
* api: Update policy endpoint to permit anonymous access [[GH-6021](https://github.com/hashicorp/nomad/issues/6021)]
* build: Updated to Go 1.12.13 [[GH-6606](https://github.com/hashicorp/nomad/issues/6606)]
* consul: Add support for service `canary_meta`
* cli: Show full ID in node and alloc individual status views [[GH-6425](https://github.com/hashicorp/nomad/issues/6425)]
* client: Enable setting tags on Consul Connect sidecar service [[GH-6448](https://github.com/hashicorp/nomad/issues/6448)]
* client: Added support for downloading artifacts from Google Cloud Storage [[GH-6692](https://github.com/hashicorp/nomad/pull/6692)]
Expand Down
1 change: 1 addition & 0 deletions api/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ type Service struct {
CheckRestart *CheckRestart `mapstructure:"check_restart"`
Connect *ConsulConnect
Meta map[string]string
CanaryMeta map[string]string
}

// Canonicalize the Service by ensuring its name and address mode are set. Task
Expand Down
2 changes: 2 additions & 0 deletions command/agent/consul/catalog_testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"sync"

log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/nomad/helper"

"github.com/hashicorp/consul/api"
)
Expand Down Expand Up @@ -111,6 +112,7 @@ func (c *MockAgent) Services() (map[string]*api.AgentService, error) {
ID: v.ID,
Service: v.Name,
Tags: make([]string, len(v.Tags)),
Meta: helper.CopyMapStringString(v.Meta),
Port: v.Port,
Address: v.Address,
EnableTagOverride: v.EnableTagOverride,
Expand Down
18 changes: 14 additions & 4 deletions command/agent/consul/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ func agentServiceUpdateRequired(reg *api.AgentServiceRegistration, svc *api.Agen
reg.Port == svc.Port &&
reg.Address == svc.Address &&
reg.Name == svc.Service &&
reflect.DeepEqual(reg.Tags, svc.Tags))
reflect.DeepEqual(reg.Tags, svc.Tags) &&
reflect.DeepEqual(reg.Meta, svc.Meta))
}

// operations are submitted to the main loop via commit() for synchronizing
Expand Down Expand Up @@ -713,9 +714,18 @@ func (c *ServiceClient) serviceRegs(ops *operations, service *structs.Service, w
return nil, fmt.Errorf("invalid Consul Connect configuration for service %q: %v", service.Name, err)
}

meta := make(map[string]string, len(service.Meta))
for k, v := range service.Meta {
meta[k] = v
// Determine whether to use meta or canary_meta
var meta map[string]string
if workload.Canary && len(service.CanaryMeta) > 0 {
meta = make(map[string]string, len(service.CanaryMeta)+1)
for k, v := range service.CanaryMeta {
meta[k] = v
}
} else {
meta = make(map[string]string, len(service.Meta)+1)
for k, v := range service.Meta {
meta[k] = v
}
}

// This enables the consul UI to show that Nomad registered this service
Expand Down
68 changes: 68 additions & 0 deletions command/agent/consul/unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func testWorkload() *WorkloadServices {
Name: "taskname-service",
PortLabel: "x",
Tags: []string{"tag1", "tag2"},
Meta: map[string]string{"meta1": "foo"},
},
},
Networks: []*structs.NetworkResource{
Expand Down Expand Up @@ -1077,6 +1078,73 @@ func TestConsul_CanaryTags_NoTags(t *testing.T) {
require.Len(ctx.FakeConsul.services, 0)
}

// TestConsul_CanaryMeta asserts CanaryMeta are used when Canary=true
func TestConsul_CanaryMeta(t *testing.T) {
t.Parallel()
require := require.New(t)
ctx := setupFake(t)

canaryMeta := map[string]string{"meta1": "canary"}
canaryMeta["external-source"] = "nomad"
ctx.Workload.Canary = true
ctx.Workload.Services[0].CanaryMeta = canaryMeta

require.NoError(ctx.ServiceClient.RegisterWorkload(ctx.Workload))
require.NoError(ctx.syncOnce())
require.Len(ctx.FakeConsul.services, 1)
for _, service := range ctx.FakeConsul.services {
require.Equal(canaryMeta, service.Meta)
}

// Disable canary and assert meta are not the canary meta
origWorkload := ctx.Workload.Copy()
ctx.Workload.Canary = false
require.NoError(ctx.ServiceClient.UpdateWorkload(origWorkload, ctx.Workload))
require.NoError(ctx.syncOnce())
require.Len(ctx.FakeConsul.services, 1)
for _, service := range ctx.FakeConsul.services {
require.NotEqual(canaryMeta, service.Meta)
}

ctx.ServiceClient.RemoveWorkload(ctx.Workload)
require.NoError(ctx.syncOnce())
require.Len(ctx.FakeConsul.services, 0)
}

// TestConsul_CanaryMeta_NoMeta asserts Meta are used when Canary=true and there
// are no specified canary meta
func TestConsul_CanaryMeta_NoMeta(t *testing.T) {
t.Parallel()
require := require.New(t)
ctx := setupFake(t)

meta := map[string]string{"meta1": "foo"}
meta["external-source"] = "nomad"
ctx.Workload.Canary = true
ctx.Workload.Services[0].Meta = meta

require.NoError(ctx.ServiceClient.RegisterWorkload(ctx.Workload))
require.NoError(ctx.syncOnce())
require.Len(ctx.FakeConsul.services, 1)
for _, service := range ctx.FakeConsul.services {
require.Equal(meta, service.Meta)
}

// Disable canary and assert meta dont change
origWorkload := ctx.Workload.Copy()
ctx.Workload.Canary = false
require.NoError(ctx.ServiceClient.UpdateWorkload(origWorkload, ctx.Workload))
require.NoError(ctx.syncOnce())
require.Len(ctx.FakeConsul.services, 1)
for _, service := range ctx.FakeConsul.services {
require.Equal(meta, service.Meta)
}

ctx.ServiceClient.RemoveWorkload(ctx.Workload)
require.NoError(ctx.syncOnce())
require.Len(ctx.FakeConsul.services, 0)
}

// TestConsul_PeriodicSync asserts that Nomad periodically reconciles with
// Consul.
func TestConsul_PeriodicSync(t *testing.T) {
Expand Down
2 changes: 2 additions & 0 deletions command/agent/job_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,7 @@ func ApiTaskToStructsTask(apiTask *api.Task, structsTask *structs.Task) {
CanaryTags: service.CanaryTags,
AddressMode: service.AddressMode,
Meta: helper.CopyMapStringString(service.Meta),
CanaryMeta: helper.CopyMapStringString(service.CanaryMeta),
}

if l := len(service.Checks); l != 0 {
Expand Down Expand Up @@ -1012,6 +1013,7 @@ func ApiServicesToStructs(in []*api.Service) []*structs.Service {
CanaryTags: s.CanaryTags,
AddressMode: s.AddressMode,
Meta: helper.CopyMapStringString(s.Meta),
CanaryMeta: helper.CopyMapStringString(s.CanaryMeta),
}

if l := len(s.Checks); l != 0 {
Expand Down
16 changes: 16 additions & 0 deletions jobspec/parse_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func parseService(o *ast.ObjectItem) (*api.Service, error) {
"check_restart",
"connect",
"meta",
"canary_meta",
}
if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
return nil, err
Expand All @@ -62,6 +63,7 @@ func parseService(o *ast.ObjectItem) (*api.Service, error) {
delete(m, "check_restart")
delete(m, "connect")
delete(m, "meta")
delete(m, "canary_meta")

if err := mapstructure.WeakDecode(m, &service); err != nil {
return nil, err
Expand Down Expand Up @@ -122,6 +124,20 @@ func parseService(o *ast.ObjectItem) (*api.Service, error) {
}
}

// Parse out canary_meta fields. These are in HCL as a list so we need
// to iterate over them and merge them.
if metaO := listVal.Filter("canary_meta"); len(metaO.Items) > 0 {
for _, o := range metaO.Elem().Items {
var m map[string]interface{}
if err := hcl.DecodeObject(&m, o.Val); err != nil {
return nil, err
}
if err := mapstructure.WeakDecode(m, &service.CanaryMeta); err != nil {
return nil, err
}
}
}

return &service, nil
}

Expand Down
8 changes: 7 additions & 1 deletion jobspec/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,13 @@ func TestParse(t *testing.T) {
{
Tags: []string{"foo", "bar"},
CanaryTags: []string{"canary", "bam"},
PortLabel: "http",
Meta: map[string]string{
"abc": "123",
},
CanaryMeta: map[string]string{
"canary": "boom",
},
PortLabel: "http",
Checks: []api.ServiceCheck{
{
Name: "check-name",
Expand Down
8 changes: 8 additions & 0 deletions jobspec/test-fixtures/basic.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,14 @@ job "binstore-storagelocker" {
}

service {
meta {
abc = "123"
}

canary_meta {
canary = "boom"
}

tags = ["foo", "bar"]
canary_tags = ["canary", "bam"]
port = "http"
Expand Down
9 changes: 9 additions & 0 deletions nomad/structs/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ type Service struct {
Checks []*ServiceCheck // List of checks associated with the service
Connect *ConsulConnect // Consul Connect configuration
Meta map[string]string // Consul service meta
CanaryMeta map[string]string // Consul service meta when it is a canary
}

// Copy the stanza recursively. Returns nil if nil.
Expand All @@ -354,6 +355,7 @@ func (s *Service) Copy() *Service {
ns.Connect = s.Connect.Copy()

ns.Meta = helper.CopyMapStringString(s.Meta)
ns.CanaryMeta = helper.CopyMapStringString(s.CanaryMeta)

return ns
}
Expand Down Expand Up @@ -466,6 +468,9 @@ func (s *Service) Hash(allocID, taskName string, canary bool) string {
if len(s.Meta) > 0 {
fmt.Fprintf(h, "%v", s.Meta)
}
if len(s.CanaryMeta) > 0 {
fmt.Fprintf(h, "%v", s.CanaryMeta)
}

// Vary ID on whether or not CanaryTags will be used
if canary {
Expand Down Expand Up @@ -526,6 +531,10 @@ OUTER:
return false
}

if !reflect.DeepEqual(s.CanaryMeta, o.CanaryMeta) {
return false
}

if !helper.CompareSliceSetString(s.Tags, o.Tags) {
return false
}
Expand Down
6 changes: 6 additions & 0 deletions website/source/api/json-jobs.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,12 @@ The `Task` object supports the following keys:
updated to the set defined in the `Tags` field. String interpolation is
supported in tags.

- `CanaryMeta`: A key-value map that annotates this Service while it
is a canary. Once the canary is promoted, the registered meta will be
updated to the set defined in the `Meta` field or removed if the `Meta`
field is not set. String interpolation is supported in meta keys and
values.

- `PortLabel`: `PortLabel` is an optional string and is used to associate
a port with the service. If specified, the port label must match one
defined in the resources block. This could be a label of either a
Expand Down
9 changes: 8 additions & 1 deletion website/source/docs/job-specification/service.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ Connect][connect] integration.
this service when the service is part of an allocation that is currently a
canary. Once the canary is promoted, the registered tags will be updated to
those specified in the `tags` parameter. If this is not supplied, the
registered tags will be equal to that of the `tags parameter.
registered tags will be equal to that of the `tags` parameter.

- `address_mode` `(string: "auto")` - Specifies what address (host or
driver-specific) this service should advertise. This setting is supported in
Expand All @@ -151,6 +151,13 @@ Connect][connect] integration.
- `meta` <code>([Meta][]: nil)</code> - Specifies a key-value map that annotates
the Consul service with user-defined metadata.

- `canary_meta` <code>([Meta][]: nil)</code> - Specifies a key-value map that
annotates the Consul service with user-defined metadata when the service is
part of an allocation that is currently a canary. Once the canary is
promoted, the registered meta will be updated to those specified in the
`meta` parameter. If this is not supplied, the registered meta will be set to
that of the `meta` parameter.

### `check` Parameters

Note that health checks run inside the task. If your task is a Docker container,
Expand Down