Skip to content

Commit

Permalink
Merge pull request #6690 from hashicorp/f-canary-meta
Browse files Browse the repository at this point in the history
consul: add support for canary meta
  • Loading branch information
nickethier committed Jan 28, 2020
2 parents 5e789c3 + d6dd9b6 commit 3aec977
Show file tree
Hide file tree
Showing 12 changed files with 142 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,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

0 comments on commit 3aec977

Please sign in to comment.