From db703a5117110ffd1ffd88162d7d81441dcb0585 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Tue, 9 Feb 2021 17:44:55 +0300 Subject: [PATCH] [#316] ir/netmap: Validate LOCODE attributes of network map candidates Define a structure for dealing with the geographic location of nodes. Implement VerifyAndUpdate (with the same purpose as NodeValidator interface) that checks LOCODE attribute and fills other attributes of the location. Technically the entity is a wrapper over the NeoFS location database: it maps the node LOCODE to the database record from which the new attributes are generated. Signed-off-by: Leonard Lyubich --- .../netmap/nodevalidation/locode/calls.go | 85 +++++++++++++++++++ .../netmap/nodevalidation/locode/deps.go | 59 +++++++++++++ .../netmap/nodevalidation/locode/util.go | 34 ++++++++ .../netmap/nodevalidation/locode/validator.go | 67 +++++++++++++++ 4 files changed, 245 insertions(+) create mode 100644 pkg/innerring/processors/netmap/nodevalidation/locode/calls.go create mode 100644 pkg/innerring/processors/netmap/nodevalidation/locode/deps.go create mode 100644 pkg/innerring/processors/netmap/nodevalidation/locode/util.go create mode 100644 pkg/innerring/processors/netmap/nodevalidation/locode/validator.go diff --git a/pkg/innerring/processors/netmap/nodevalidation/locode/calls.go b/pkg/innerring/processors/netmap/nodevalidation/locode/calls.go new file mode 100644 index 0000000000..318a2cae43 --- /dev/null +++ b/pkg/innerring/processors/netmap/nodevalidation/locode/calls.go @@ -0,0 +1,85 @@ +package locode + +import ( + "github.com/nspcc-dev/neofs-api-go/pkg/netmap" + "github.com/nspcc-dev/neofs-node/pkg/util/locode" + "github.com/pkg/errors" +) + +var errMissingLocode = errors.New("missing locode attribute") + +var errMissingRequiredAttr = errors.New("missing required attribute in DB record") + +// VerifyAndUpdate validates LOCODE attribute of n +// and adds a group of related attributes. +// +// If n does not contain LOCODE attribute or its value does not +// match the UN/LOCODE format, an error returns. +// +// New attributes are formed from the record of DB instance (Prm). +// If DB entry R was found w/o errors, then new attributes are: +// * CountryCode: R.CountryCode().String(); +// * Country: R.CountryName(); +// * CityCode: R.LocationCode().String(); +// * City: Record.LocationName(); +// * SubDivCode: R.SubDivCode(); +// * SubDiv: R.SubDivName(); +// * Continent: R.Continent().String(). +// +// LOCODE attribute remains untouched. +func (v *Validator) VerifyAndUpdate(n *netmap.NodeInfo) error { + mAttr := uniqueAttributes(n.Attributes()) + + attrLocode, ok := mAttr[attrKeyLocode] + if !ok { + return errMissingLocode + } + + lc, err := locode.FromString(attrLocode.Value()) + if err != nil { + return errors.Wrap(err, "invalid locode value") + } + + record, err := v.db.Get(lc) + if err != nil { + return errors.Wrap(err, "could not get locode record from DB") + } + + for attrKey, attrDesc := range v.mAttr { + attrVal := attrDesc.converter(record) + if attrVal == "" { + if !attrDesc.optional { + return errMissingRequiredAttr + } + + continue + } + + a := netmap.NewNodeAttribute() + a.SetKey(attrKey) + a.SetValue(attrVal) + + mAttr[attrKey] = a + } + + as := n.Attributes() + as = as[:0] + + for _, attr := range mAttr { + as = append(as, attr) + } + + n.SetAttributes(as...) + + return nil +} + +func uniqueAttributes(as []*netmap.NodeAttribute) map[string]*netmap.NodeAttribute { + mAttr := make(map[string]*netmap.NodeAttribute, len(as)) + + for _, attr := range as { + mAttr[attr.Key()] = attr + } + + return mAttr +} diff --git a/pkg/innerring/processors/netmap/nodevalidation/locode/deps.go b/pkg/innerring/processors/netmap/nodevalidation/locode/deps.go new file mode 100644 index 0000000000..5a5078ff8d --- /dev/null +++ b/pkg/innerring/processors/netmap/nodevalidation/locode/deps.go @@ -0,0 +1,59 @@ +package locode + +import ( + "github.com/nspcc-dev/neofs-node/pkg/util/locode" + locodedb "github.com/nspcc-dev/neofs-node/pkg/util/locode/db" +) + +// Record is an interface of read-only +// NeoFS LOCODE database single entry. +type Record interface { + // Must return ISO 3166-1 alpha-2 + // country code. + // + // Must not return nil. + CountryCode() *locodedb.CountryCode + + // Must return English short country name + // officially used by the ISO 3166 + // Maintenance Agency (ISO 3166/MA). + CountryName() string + + // Must return UN/LOCODE 3-character code + // for the location (numerals 2-9 may also + // be used). + // + // Must not return nil. + LocationCode() *locodedb.LocationCode + + // Must return name of the location which + // have been allocated a UN/LOCODE without + // diacritic sign. + LocationName() string + + // Must return ISO 1-3 character alphabetic + // and/or numeric code for the administrative + // division of the country concerned. + SubDivCode() string + + // Must return subdivision name. + SubDivName() string + + // Must return existing continent where is + // the location. + // + // Must not return nil. + Continent() *locodedb.Continent +} + +// DB is an interface of read-only +// NeoFS LOCODE database. +type DB interface { + // Must find the record that corresponds to + // LOCODE and provides the Record interface. + // + // Must return an error if Record is nil. + // + // LOCODE is always non-nil. + Get(*locode.LOCODE) (Record, error) +} diff --git a/pkg/innerring/processors/netmap/nodevalidation/locode/util.go b/pkg/innerring/processors/netmap/nodevalidation/locode/util.go new file mode 100644 index 0000000000..40d81601f6 --- /dev/null +++ b/pkg/innerring/processors/netmap/nodevalidation/locode/util.go @@ -0,0 +1,34 @@ +package locode + +type attrDescriptor struct { + optional bool + converter func(Record) string +} + +func countryCodeValue(r Record) (val string) { + return r.CountryCode().String() +} + +func countryValue(r Record) string { + return r.CountryName() +} + +func locationCodeValue(r Record) string { + return r.LocationCode().String() +} + +func locationValue(r Record) string { + return r.LocationName() +} + +func subDivCodeValue(r Record) string { + return r.SubDivCode() +} + +func subDivValue(r Record) string { + return r.SubDivName() +} + +func continentValue(r Record) string { + return r.Continent().String() +} diff --git a/pkg/innerring/processors/netmap/nodevalidation/locode/validator.go b/pkg/innerring/processors/netmap/nodevalidation/locode/validator.go new file mode 100644 index 0000000000..e25878c9ea --- /dev/null +++ b/pkg/innerring/processors/netmap/nodevalidation/locode/validator.go @@ -0,0 +1,67 @@ +package locode + +// Prm groups the required parameters of the Validator's constructor. +// +// All values must comply with the requirements imposed on them. +// Passing incorrect parameter values will result in constructor +// failure (error or panic depending on the implementation). +type Prm struct { + // NeoFS LOCODE database interface. + // + // Must not be nil. + DB DB +} + +// Validator is an utility that verifies and updates +// node attributes associated with its geographical location +// (LOCODE). +// +// For correct operation, Validator must be created +// using the constructor (New) based on the required parameters +// and optional components. After successful creation, +// the Validator is immediately ready to work through API. +type Validator struct { + db DB + + mAttr map[string]attrDescriptor +} + +// TODO: define constants in API lib. +const ( + attrKeyLocode = "Locode" + + attrKeyCountryCode = "CountryCode" + attrKeyCountry = "Country" + + attrKeyLocationCode = "CityCode" + attrKeyLocation = "City" + + attrKeySubDivCode = "SubDivCode" + attrKeySubDiv = "SubDiv" + + attrKeyContinent = "Continent" +) + +// New creates a new instance of the Validator. +// +// Panics if at least one value of the parameters is invalid. +// +// The created Validator does not require additional +// initialization and is completely ready for work. +func New(prm Prm) *Validator { + return &Validator{ + db: prm.DB, + mAttr: map[string]attrDescriptor{ + attrKeyCountryCode: {converter: countryCodeValue}, + attrKeyCountry: {converter: countryValue}, + + attrKeyLocationCode: {converter: locationCodeValue}, + attrKeyLocation: {converter: locationValue}, + + attrKeySubDivCode: {converter: subDivCodeValue, optional: true}, + attrKeySubDiv: {converter: subDivValue, optional: true}, + + attrKeyContinent: {converter: continentValue}, + }, + } +}