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

Add geo fields to add_host_metadata processor. #9392

Merged
merged 2 commits into from
Dec 14, 2018
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
36 changes: 35 additions & 1 deletion libbeat/docs/processors-using.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,14 @@ processors:
- add_host_metadata:
netinfo.enabled: false
cache.ttl: 5m
geo:
name: nyc-dc1-rack1
location: 40.7128, -74.0060
continent_name: North America
country_iso_code: US
region_name: New York
region_iso_code: NY
city_name: New York
-------------------------------------------------------------------------------

It has the following settings:
Expand All @@ -903,6 +911,23 @@ It has the following settings:

`cache.ttl`:: (Optional) The processor uses an internal cache for the host metadata. This sets the cache expiration time. The default is 5m, negative values disable caching altogether.

`geo.name`:: User definable token to be used for identifying a discrete location. Frequently a datacenter, rack, or similar.

`geo.location`:: Longitude and latitude in comma separated format.

`geo.continent_name`:: Name of the continent.

`geo.country_name`:: Name of the country.

`geo.region_name`:: Name of the region.

`geo.city_name`:: Name of the city.

`geo.country_iso_code`:: ISO country code.

`geo.region_iso_code`:: ISO region code.


The `add_host_metadata` processor annotates each event with relevant metadata from the host machine.
The fields added to the event are looking as following:

Expand All @@ -922,7 +947,16 @@ The fields added to the event are looking as following:
"name":"Mac OS X"
},
"ip": ["192.168.0.1", "10.0.0.1"],
"mac": ["00:25:96:12:34:56", "72:00:06:ff:79:f1"]
"mac": ["00:25:96:12:34:56", "72:00:06:ff:79:f1"],
"geo": {
"continent_name": "North America",
"country_iso_code": "US",
"region_name": "New York",
"region_iso_code": "NY",
"city_name": "New York",
"name": "nyc-dc1-rack1",
"location": "40.7128, -74.0060"
}
}
}
-------------------------------------------------------------------------------
Expand Down
50 changes: 48 additions & 2 deletions libbeat/processors/add_host_metadata/add_host_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package add_host_metadata
import (
"fmt"
"net"
"regexp"
"sync"
"time"

Expand All @@ -43,8 +44,9 @@ type addHostMetadata struct {
time.Time
sync.Mutex
}
data common.MapStrPointer
config Config
data common.MapStrPointer
geoData common.MapStr
config Config
}

const (
Expand All @@ -62,6 +64,46 @@ func newHostMetadataProcessor(cfg *common.Config) (processors.Processor, error)
data: common.NewMapStrPointer(nil),
}
p.loadData()

if config.Geo != nil {
if len(config.Geo.Location) > 0 {
// Regexp matching a number with an optional decimal component
// Valid numbers: '123', '123.23', etc.
latOrLon := `\-?\d+(\.\d+)?`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. Thanks for all the details here.


// Regexp matching a pair of lat lon coordinates.
// e.g. 40.123, -92.929
locRegexp := `^\s*` + // anchor to start of string with optional whitespace
latOrLon + // match the latitude
`\s*\,\s*` + // match the separator. optional surrounding whitespace
latOrLon + // match the longitude
`\s*$` //optional whitespace then end anchor

if m, _ := regexp.MatchString(locRegexp, config.Geo.Location); !m {
return nil, errors.New(fmt.Sprintf("Invalid lat,lon string for add_host_metadata: %s", config.Geo.Location))
}
}

geoFields := common.MapStr{
"name": config.Geo.Name,
"location": config.Geo.Location,
"continent_name": config.Geo.ContinentName,
"country_iso_code": config.Geo.CountryISOCode,
"region_name": config.Geo.RegionName,
"region_iso_code": config.Geo.RegionISOCode,
"city_name": config.Geo.CityName,
}
// Delete any empty values
blankStringMatch := regexp.MustCompile(`^\s*$`)
for k, v := range geoFields {
vStr := v.(string)
if blankStringMatch.MatchString(vStr) {
delete(geoFields, k)
}
}
p.geoData = common.MapStr{"host": common.MapStr{"geo": geoFields}}
}

return p, nil
}

Expand All @@ -73,6 +115,10 @@ func (p *addHostMetadata) Run(event *beat.Event) (*beat.Event, error) {
}

event.Fields.DeepUpdate(p.data.Get().Clone())

if len(p.geoData) > 0 {
event.Fields.DeepUpdate(p.geoData)
}
return event, nil
}

Expand Down
103 changes: 103 additions & 0 deletions libbeat/processors/add_host_metadata/add_host_metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@
package add_host_metadata

import (
"fmt"
"runtime"
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/stretchr/testify/assert"

"github.com/elastic/beats/libbeat/beat"
Expand Down Expand Up @@ -112,3 +115,103 @@ func TestConfigNetInfoEnabled(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, v)
}

func TestConfigGeoEnabled(t *testing.T) {
event := &beat.Event{
Fields: common.MapStr{},
Timestamp: time.Now(),
}

config := map[string]interface{}{
"geo.name": "yerevan-am",
"geo.location": "40.177200, 44.503490",
"geo.continent_name": "Asia",
"geo.country_iso_code": "AM",
"geo.region_name": "Erevan",
"geo.region_iso_code": "AM-ER",
"geo.city_name": "Yerevan",
}

testConfig, err := common.NewConfigFrom(config)
assert.NoError(t, err)

p, err := newHostMetadataProcessor(testConfig)
require.NoError(t, err)

newEvent, err := p.Run(event)
assert.NoError(t, err)

for configKey, configValue := range config {
t.Run(fmt.Sprintf("Check of %s", configKey), func(t *testing.T) {
v, err := newEvent.GetValue(fmt.Sprintf("host.%s", configKey))
assert.NoError(t, err)
assert.Equal(t, configValue, v, "Could not find in %s", newEvent)
})
}
}

func TestPartialGeo(t *testing.T) {
event := &beat.Event{
Fields: common.MapStr{},
Timestamp: time.Now(),
}

config := map[string]interface{}{
"geo.name": "yerevan-am",
"geo.city_name": " ",
}

testConfig, err := common.NewConfigFrom(config)
assert.NoError(t, err)

p, err := newHostMetadataProcessor(testConfig)
require.NoError(t, err)

newEvent, err := p.Run(event)
assert.NoError(t, err)

v, err := newEvent.Fields.GetValue("host.geo.name")
assert.NoError(t, err)
assert.Equal(t, "yerevan-am", v)

missing := []string{"continent_name", "country_name", "country_iso_code", "region_name", "region_iso_code", "city_name"}

for _, k := range missing {
path := "host.geo." + k
v, err = newEvent.Fields.GetValue(path)

assert.Equal(t, common.ErrKeyNotFound, err, "din expect to find %v", path)
}
}

func TestGeoLocationValidation(t *testing.T) {
locations := []struct {
str string
valid bool
}{
{"40.177200, 44.503490", true},
{"-40.177200, -44.503490", true},
{"garbage", false},
{"9999999999", false},
}

for _, location := range locations {
t.Run(fmt.Sprintf("Location %s validation should be %t", location.str, location.valid), func(t *testing.T) {

conf, err := common.NewConfigFrom(map[string]interface{}{
"geo": map[string]interface{}{
"location": location.str,
},
})
require.NoError(t, err)

_, err = newHostMetadataProcessor(conf)

if location.valid {
require.NoError(t, err)
} else {
require.Error(t, err)
}
})
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice tests.

Perhaps one more to test "Delete any empty values"? Make sure your empty values include things like empty spaces too {"city_name": " "}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@webmat I've added handling + tests for blank strings. Mind taking a look?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah good stuff.

Seems like I noticed the "din expect" typo too late for this PR ;-)

12 changes: 12 additions & 0 deletions libbeat/processors/add_host_metadata/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ import (
type Config struct {
NetInfoEnabled bool `config:"netinfo.enabled"` // Add IP and MAC to event
CacheTTL time.Duration `config:"cache.ttl"`
Geo *GeoConfig `config:"geo"`
}

// GeoConfig contains geo configuration data.
type GeoConfig struct {
andrewvc marked this conversation as resolved.
Show resolved Hide resolved
Name string `config:"name"`
Location string `config:"location"`
ContinentName string `config:"continent_name"`
CountryISOCode string `config:"country_iso_code"`
RegionName string `config:"region_name"`
RegionISOCode string `config:"region_iso_code"`
CityName string `config:"city_name"`
}

func defaultConfig() Config {
Expand Down