consul: add support for canary meta

This commit is contained in:
Nick Ethier
2019-11-12 22:27:54 -05:00
parent 9fdc9dea4e
commit 64f4e9e691
11 changed files with 135 additions and 6 deletions

View File

@@ -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)]

View File

@@ -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

View File

@@ -5,6 +5,7 @@ import (
"sync"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/consul/api"
)
@@ -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,

View File

@@ -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
@@ -713,9 +714,17 @@ 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
var meta map[string]string
if task.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

View File

@@ -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{
@@ -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.Task.Canary = true
ctx.Task.Services[0].CanaryMeta = canaryMeta
require.NoError(ctx.ServiceClient.RegisterTask(ctx.Task))
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
origTask := ctx.Task.Copy()
ctx.Task.Canary = false
require.NoError(ctx.ServiceClient.UpdateTask(origTask, ctx.Task))
require.NoError(ctx.syncOnce())
require.Len(ctx.FakeConsul.services, 1)
for _, service := range ctx.FakeConsul.services {
require.NotEqual(canaryMeta, service.Meta)
}
ctx.ServiceClient.RemoveTask(ctx.Task)
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.Task.Canary = true
ctx.Task.Services[0].Meta = meta
require.NoError(ctx.ServiceClient.RegisterTask(ctx.Task))
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
origTask := ctx.Task.Copy()
ctx.Task.Canary = false
require.NoError(ctx.ServiceClient.UpdateTask(origTask, ctx.Task))
require.NoError(ctx.syncOnce())
require.Len(ctx.FakeConsul.services, 1)
for _, service := range ctx.FakeConsul.services {
require.Equal(meta, service.Meta)
}
ctx.ServiceClient.RemoveTask(ctx.Task)
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) {

View File

@@ -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 {
@@ -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 {

View File

@@ -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
@@ -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
@@ -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
}

View File

@@ -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",

View File

@@ -169,6 +169,14 @@ job "binstore-storagelocker" {
}
service {
meta {
abc = "123"
}
canary_meta {
canary = "boom"
}
tags = ["foo", "bar"]
canary_tags = ["canary", "bam"]
port = "http"

View File

@@ -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.
@@ -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
}
@@ -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 {
@@ -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
}

View File

@@ -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
@@ -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 supploed, 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,