Merge pull request #4047 from pierresouchay/added_missing_meta_in_service_definition

[BUGFIX] Added Service Meta support in configuration files
This commit is contained in:
Paul Banks 2018-04-25 13:08:53 +01:00 committed by GitHub
commit c8db140ff7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 93 additions and 10 deletions

View File

@ -155,9 +155,18 @@ func (s *HTTPServer) AgentServices(resp http.ResponseWriter, req *http.Request)
// Use empty list instead of nil
for id, s := range services {
if s.Tags == nil {
if s.Tags == nil || s.Meta == nil {
clone := *s
if s.Tags == nil {
clone.Tags = make([]string, 0)
} else {
clone.Tags = s.Tags
}
if s.Meta == nil {
clone.Meta = make(map[string]string)
} else {
clone.Meta = s.Meta
}
services[id] = &clone
}
}

View File

@ -1204,6 +1204,7 @@ func TestAgent_RegisterService(t *testing.T) {
args := &structs.ServiceDefinition{
Name: "test",
Meta: map[string]string{"hello": "world"},
Tags: []string{"master"},
Port: 8000,
Check: structs.CheckType{
@ -1232,6 +1233,9 @@ func TestAgent_RegisterService(t *testing.T) {
if _, ok := a.State.Services()["test"]; !ok {
t.Fatalf("missing test service")
}
if val := a.State.Service("test").Meta["hello"]; val != "world" {
t.Fatalf("Missing meta: %v", a.State.Service("test").Meta)
}
// Ensure we have a check mapping
checks := a.State.Checks()
@ -1254,7 +1258,7 @@ func TestAgent_RegisterService_TranslateKeys(t *testing.T) {
a := NewTestAgent(t.Name(), "")
defer a.Shutdown()
json := `{"name":"test", "port":8000, "enable_tag_override": true}`
json := `{"name":"test", "port":8000, "enable_tag_override": true, "meta": {"some": "meta"}}`
req, _ := http.NewRequest("PUT", "/v1/agent/service/register", strings.NewReader(json))
obj, err := a.srv.AgentRegisterService(nil, req)
@ -1264,10 +1268,10 @@ func TestAgent_RegisterService_TranslateKeys(t *testing.T) {
if obj != nil {
t.Fatalf("bad: %v", obj)
}
svc := &structs.NodeService{
ID: "test",
Service: "test",
Meta: map[string]string{"some": "meta"},
Port: 8000,
EnableTagOverride: true,
}

View File

@ -998,11 +998,18 @@ func (b *Builder) serviceVal(v *ServiceDefinition) *structs.ServiceDefinition {
checks = append(checks, b.checkVal(v.Check).CheckType())
}
meta := make(map[string]string)
if err := structs.ValidateMetadata(v.Meta, false); err != nil {
b.err = multierror.Append(fmt.Errorf("invalid meta for service %s: %v", b.stringVal(v.Name), err))
} else {
meta = v.Meta
}
return &structs.ServiceDefinition{
ID: b.stringVal(v.ID),
Name: b.stringVal(v.Name),
Tags: v.Tags,
Address: b.stringVal(v.Address),
Meta: meta,
Port: b.intVal(v.Port),
Token: b.stringVal(v.Token),
EnableTagOverride: b.boolVal(v.EnableTagOverride),

View File

@ -319,6 +319,7 @@ type ServiceDefinition struct {
Name *string `json:"name,omitempty" hcl:"name" mapstructure:"name"`
Tags []string `json:"tags,omitempty" hcl:"tags" mapstructure:"tags"`
Address *string `json:"address,omitempty" hcl:"address" mapstructure:"address"`
Meta map[string]string `json:"meta,omitempty" hcl:"meta" mapstructure:"meta"`
Port *int `json:"port,omitempty" hcl:"port" mapstructure:"port"`
Check *CheckDefinition `json:"check,omitempty" hcl:"check" mapstructure:"check"`
Checks []CheckDefinition `json:"checks,omitempty" hcl:"checks" mapstructure:"checks"`

View File

@ -1923,20 +1923,59 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
},
json: []string{
`{ "service": { "name": "a", "port": 80 } }`,
`{ "service": { "name": "b", "port": 90 } }`,
`{ "service": { "name": "b", "port": 90, "meta": {"my": "value"} } }`,
},
hcl: []string{
`service = { name = "a" port = 80 }`,
`service = { name = "b" port = 90 }`,
`service = { name = "b" port = 90 meta={my="value"}}`,
},
patch: func(rt *RuntimeConfig) {
rt.Services = []*structs.ServiceDefinition{
&structs.ServiceDefinition{Name: "a", Port: 80},
&structs.ServiceDefinition{Name: "b", Port: 90},
&structs.ServiceDefinition{Name: "b", Port: 90, Meta: map[string]string{"my": "value"}},
}
rt.DataDir = dataDir
},
},
{
desc: "service with wrong meta: too long key",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{
`{ "service": { "name": "a", "port": 80, "meta": { "` + randomString(520) + `": "metaValue" } } }`,
},
hcl: []string{
`service = { name = "a" port = 80, meta={` + randomString(520) + `="metaValue"} }`,
},
err: `Key is too long`,
},
{
desc: "service with wrong meta: too long value",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{
`{ "service": { "name": "a", "port": 80, "meta": { "a": "` + randomString(520) + `" } } }`,
},
hcl: []string{
`service = { name = "a" port = 80, meta={a="` + randomString(520) + `"} }`,
},
err: `Value is too long`,
},
{
desc: "service with wrong meta: too many meta",
args: []string{
`-data-dir=` + dataDir,
},
json: []string{
`{ "service": { "name": "a", "port": 80, "meta": { ` + metaPairs(70, "json") + `} } }`,
},
hcl: []string{
`service = { name = "a" port = 80 meta={` + metaPairs(70, "hcl") + `} }`,
},
err: `invalid meta for service a: Node metadata cannot contain more than 64 key`,
},
{
desc: "translated keys",
args: []string{
@ -2397,6 +2436,9 @@ func TestFullConfig(t *testing.T) {
"service": {
"id": "dLOXpSCI",
"name": "o1ynPkp0",
"meta": {
"mymeta": "data"
},
"tags": ["nkwshvM5", "NTDWn3ek"],
"address": "cOlSOhbp",
"token": "msy7iWER",
@ -2835,6 +2877,9 @@ func TestFullConfig(t *testing.T) {
service = {
id = "dLOXpSCI"
name = "o1ynPkp0"
meta = {
mymeta = "data"
}
tags = ["nkwshvM5", "NTDWn3ek"]
address = "cOlSOhbp"
token = "msy7iWER"
@ -3489,6 +3534,7 @@ func TestFullConfig(t *testing.T) {
Tags: []string{"nkwshvM5", "NTDWn3ek"},
Address: "cOlSOhbp",
Token: "msy7iWER",
Meta: map[string]string{"mymeta": "data"},
Port: 24237,
EnableTagOverride: true,
Checks: structs.CheckTypes{

View File

@ -678,6 +678,7 @@ func vetRegisterWithACL(rule acl.ACL, subj *structs.RegisterRequest,
ID: subj.Service.ID,
Service: subj.Service.Service,
Tags: subj.Service.Tags,
Meta: subj.Service.Meta,
Address: subj.Service.Address,
Port: subj.Service.Port,
EnableTagOverride: subj.Service.EnableTagOverride,

View File

@ -23,6 +23,7 @@ type AgentService struct {
ID string
Service string
Tags []string
Meta map[string]string
Port int
Address string
EnableTagOverride bool

View File

@ -73,7 +73,7 @@ func TestAPI_AgentReload(t *testing.T) {
agent := c.Agent()
// Update the config file with a service definition
config := `{"service":{"name":"redis", "port":1234}}`
config := `{"service":{"name":"redis", "port":1234, "Meta": {"some": "meta"}}}`
err = ioutil.WriteFile(configFile.Name(), []byte(config), 0644)
if err != nil {
t.Fatalf("err: %v", err)
@ -95,6 +95,9 @@ func TestAPI_AgentReload(t *testing.T) {
if service.Port != 1234 {
t.Fatalf("bad: %v", service.Port)
}
if service.Meta["some"] != "meta" {
t.Fatalf("Missing metadata some:=meta in %v", service)
}
}
func TestAPI_AgentMembersOpts(t *testing.T) {

View File

@ -25,6 +25,9 @@ A service definition is a script that looks like:
"name": "redis",
"tags": ["primary"],
"address": "",
"meta": {
"meta": "for my service"
}
"port": 8000,
"enable_tag_override": false,
"checks": [
@ -38,8 +41,8 @@ A service definition is a script that looks like:
```
A service definition must include a `name` and may optionally provide an
`id`, `tags`, `address`, `port`, `check`, and `enable_tag_override`. The
`id` is set to the `name` if not provided. It is required that all
`id`, `tags`, `address`, `port`, `check`, `meta` and `enable_tag_override`.
The `id` is set to the `name` if not provided. It is required that all
services have a unique ID per node, so if names might conflict then
unique IDs should be provided.
@ -57,6 +60,14 @@ The `port` field can be used as well to make a service-oriented architecture
simpler to configure; this way, the address and port of a service can
be discovered.
The `meta` object is a map of max 64 key/values with string semantics. Key can contain
only ASCII chars and no special characters (`A-Z` `a-z` `0-9` `_` and `-`).
For performance and security reasons, values as well as keys are limited to 128
characters for keys, 512 for values. This object has the same limitations as the node
meta object in node definition.
All those meta data can be retrieved individually per instance of the service
and all the instances of a given service have their own copy of it.
Services may also contain a `token` field to provide an ACL token. This token is
used for any interaction with the catalog for the service, including
[anti-entropy syncs](/docs/internals/anti-entropy.html) and deregistration.