Skip to content

Commit 5e966ca

Browse files
committed
feat(api): add SNMP configuration
As OpenConfig doesn't have SNMP configuration, we use the IETF YANG model defined in RFC 7407: https://datatracker.ietf.org/doc/rfc7407/ This review includes: - An ingestor from the NetBox-Network-CMDB SNMP API endpoint. - Generate IETF Go code from the YANG model. - A converter to map NetBox-Network-CMDB to the YANG model. - Create new endpoints: - /v1/devices/:hostname/ietfconfig - /v1/devices/:hostname/config
1 parent 56158c8 commit 5e966ca

File tree

16 files changed

+12160
-17
lines changed

16 files changed

+12160
-17
lines changed

Diff for: .golangci.yml

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ run:
1616
skip-files:
1717
- internal/model/openconfig/oc_path.go
1818
- internal/model/openconfig/oc.go
19+
- internal/model/ietf/ietf.go
1920

2021
# Invariable parameters #
2122

Diff for: Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ run:
1414
-X 'github.com/criteo/data-aggregation-api/internal/version.buildTime=$(BUILDDATE)'" \
1515
./cmd/data-aggregation-api
1616

17-
update_openconfig:
18-
./update_openconfig.sh
17+
update_yang_models:
18+
./update_yang_models.sh

Diff for: README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,4 @@ You need to update the following part in the code:
3535
4. add your convertor in `internal/convertors/...`
3636

3737
3. execute your convertor (`internal/convertors/device/device.go`):
38-
- GenerateOpenconfig()
38+
- Generateconfigs()

Diff for: internal/api/router/endpoints.go

+50
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,56 @@ func (m *Manager) getDeviceOpenConfig(w http.ResponseWriter, _ *http.Request, ps
9292
_, _ = w.Write(cfg)
9393
}
9494

95+
// getDeviceIETFConfig endpoint returns Ietf JSON for one or all devices.
96+
func (m *Manager) getDeviceIETFConfig(w http.ResponseWriter, _ *http.Request, ps httprouter.Params) {
97+
w.Header().Set(contentType, applicationJSON)
98+
hostname := ps.ByName(hostnameKey)
99+
if ps.ByName(hostnameKey) == wildcard {
100+
cfg, err := m.devices.GetAllDevicesIETFConfigJSON()
101+
if err != nil {
102+
log.Error().Err(err).Send()
103+
w.WriteHeader(http.StatusInternalServerError)
104+
return
105+
}
106+
107+
_, _ = w.Write(cfg)
108+
return
109+
}
110+
111+
cfg, err := m.devices.GetDeviceIETFConfigJSON(hostname)
112+
if err != nil {
113+
log.Error().Err(err).Send()
114+
w.WriteHeader(http.StatusInternalServerError)
115+
return
116+
}
117+
_, _ = w.Write(cfg)
118+
}
119+
120+
// getDeviceConfig endpoint returns Ietf & openconfig JSON for one or all devices.
121+
func (m *Manager) getDeviceConfig(w http.ResponseWriter, _ *http.Request, ps httprouter.Params) {
122+
w.Header().Set(contentType, applicationJSON)
123+
hostname := ps.ByName(hostnameKey)
124+
if ps.ByName(hostnameKey) == wildcard {
125+
cfg, err := m.devices.GetAllDevicesConfigJSON()
126+
if err != nil {
127+
log.Error().Err(err).Send()
128+
w.WriteHeader(http.StatusInternalServerError)
129+
return
130+
}
131+
132+
_, _ = w.Write(cfg)
133+
return
134+
}
135+
136+
cfg, err := m.devices.GetDeviceConfigJSON(hostname)
137+
if err != nil {
138+
log.Error().Err(err).Send()
139+
w.WriteHeader(http.StatusInternalServerError)
140+
return
141+
}
142+
_, _ = w.Write(cfg)
143+
}
144+
95145
// getLastReport returns the last or current report.
96146
func (m *Manager) getLastReport(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
97147
out, err := m.reports.GetLastJSON()

Diff for: internal/api/router/manager.go

+6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ type DevicesRepository interface {
2424
IsAFKEnabledJSON(hostname string) ([]byte, error)
2525
GetAllDevicesOpenConfigJSON() ([]byte, error)
2626
GetDeviceOpenConfigJSON(hostname string) ([]byte, error)
27+
GetAllDevicesIETFConfigJSON() ([]byte, error)
28+
GetDeviceIETFConfigJSON(hostname string) ([]byte, error)
29+
GetAllDevicesConfigJSON() ([]byte, error)
30+
GetDeviceConfigJSON(hostname string) ([]byte, error)
2731
}
2832

2933
type Manager struct {
@@ -56,6 +60,8 @@ func (m *Manager) ListenAndServe(ctx context.Context, address string, port int)
5660
router.GET("/api/health", healthCheck)
5761
router.GET("/v1/devices/:hostname/afk_enabled", withAuth.Wrap(m.getAFKEnabled))
5862
router.GET("/v1/devices/:hostname/openconfig", withAuth.Wrap(m.getDeviceOpenConfig))
63+
router.GET("/v1/devices/:hostname/ietfconfig", withAuth.Wrap(m.getDeviceIETFConfig))
64+
router.GET("/v1/devices/:hostname/config", withAuth.Wrap(m.getDeviceConfig))
5965
router.GET("/v1/report/last", withAuth.Wrap(m.getLastReport))
6066
router.GET("/v1/report/last/complete", withAuth.Wrap(m.getLastCompleteReport))
6167
router.GET("/v1/report/last/successful", withAuth.Wrap(m.getLastSuccessfulReport))

Diff for: internal/convertor/device/device.go

+56-9
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ import (
88

99
bgpconvertors "github.com/criteo/data-aggregation-api/internal/convertor/bgp"
1010
rpconvertors "github.com/criteo/data-aggregation-api/internal/convertor/routingpolicy"
11+
snmpconvertors "github.com/criteo/data-aggregation-api/internal/convertor/snmp"
1112
"github.com/criteo/data-aggregation-api/internal/ingestor/repository"
1213
"github.com/criteo/data-aggregation-api/internal/model/cmdb/bgp"
1314
"github.com/criteo/data-aggregation-api/internal/model/cmdb/routingpolicy"
15+
"github.com/criteo/data-aggregation-api/internal/model/cmdb/snmp"
1416
"github.com/criteo/data-aggregation-api/internal/model/dcim"
17+
"github.com/criteo/data-aggregation-api/internal/model/ietf"
1518
"github.com/criteo/data-aggregation-api/internal/model/openconfig"
1619
"github.com/openconfig/ygot/ygot"
1720
"github.com/rs/zerolog/log"
@@ -22,15 +25,18 @@ const AFKEnabledTag = "afk-enabled"
2225
var defaultInstance = "default"
2326

2427
type GeneratedConfig struct {
25-
Openconfig *openconfig.Device
26-
JSON string
28+
IETF *ietf.Device
29+
JSONIETF string
30+
Openconfig *openconfig.Device
31+
JSONOpenConfig string
2732
}
2833

2934
type Device struct {
3035
mutex *sync.Mutex
3136
Dcim *dcim.NetworkDevice
3237
Config *GeneratedConfig
3338
BGPGlobalConfig *bgp.BGPGlobal
39+
SNMP *snmp.SNMP
3440
Sessions []*bgp.Session
3541
PeerGroups []*bgp.PeerGroup
3642
PrefixLists []*routingpolicy.PrefixList
@@ -92,12 +98,16 @@ func NewDevice(dcimInfo *dcim.NetworkDevice, devicesData *repository.AssetsPerDe
9298
return nil, fmt.Errorf("no route-policies found for %s", dcimInfo.Hostname)
9399
}
94100

101+
device.SNMP, ok = devicesData.SNMP[dcimInfo.Hostname]
102+
if !ok {
103+
log.Warn().Msgf("no snmp found for %s", dcimInfo.Hostname)
104+
}
95105
return device, nil
96106
}
97107

98-
// GenerateOpenconfig generate the OpenConfig data for the current device.
108+
// Generateconfigs generate the Config (openconfig & ietf) data for the current device.
99109
// The CMDB data must have been precomputed before running this method.
100-
func (d *Device) GenerateOpenconfig() error {
110+
func (d *Device) Generateconfigs() error {
101111
d.mutex.Lock()
102112
defer d.mutex.Unlock()
103113

@@ -139,23 +149,60 @@ func (d *Device) GenerateOpenconfig() error {
139149
Indent: " ",
140150
},
141151
)
152+
142153
if err != nil {
143154
return fmt.Errorf("failed to transform an openconfig device specification (%s) into JSON using ygot: %w", d.Dcim.Hostname, err)
144155
}
145156

146157
d.Config = &GeneratedConfig{
147-
Openconfig: &config,
148-
JSON: devJSON,
158+
Openconfig: &config,
159+
JSONOpenConfig: devJSON,
160+
IETF: nil,
161+
JSONIETF: "{}",
149162
}
150163

164+
if d.SNMP == nil {
165+
log.Warn().Msgf("%s don't have a Snmp configuration, skip IetfConfig", d.Dcim.Hostname)
166+
} else {
167+
IetfSystem := snmpconvertors.SNMPtoIETFfSystem(d.SNMP)
168+
IetfSnmp := snmpconvertors.SNMPtoIETFsnmp(d.SNMP)
169+
170+
d.Config.IETF = &ietf.Device{
171+
System: &IetfSystem,
172+
Snmp: &IetfSnmp,
173+
}
174+
175+
d.Config.JSONIETF, err = ygot.EmitJSON(
176+
d.Config.IETF,
177+
&ygot.EmitJSONConfig{
178+
Format: ygot.RFC7951,
179+
SkipValidation: false,
180+
Indent: " ",
181+
},
182+
)
183+
if err != nil {
184+
return fmt.Errorf("failed to transform an ietf device specification (%s) into JSON using ygot: %w", d.Dcim.Hostname, err)
185+
}
186+
}
151187
return nil
152188
}
153189

154-
// GetCompactJSON returns OpenConfig result in not indented JSON format.
190+
// GetCompactOpenconfigJSON returns OpenConfig result in not indented JSON format.
155191
// Generated JSON is already indented by Ygot - currently there is no option to not indent the JSON.
156-
func (d *Device) GetCompactJSON() ([]byte, error) {
192+
func (d *Device) GetCompactOpenconfigJSON() ([]byte, error) {
193+
out := bytes.NewBuffer(nil)
194+
err := json.Compact(out, []byte(d.Config.JSONOpenConfig))
195+
if err != nil {
196+
return nil, err
197+
}
198+
return out.Bytes(), nil
199+
}
200+
201+
// GetCompactIETFJSON returns IETF result in not indented JSON format.
202+
// GetCompactIETFJSON JSON is already indented by Ygot - currently there is no option to not indent the JSON.
203+
func (d *Device) GetCompactIETFJSON() ([]byte, error) {
157204
out := bytes.NewBuffer(nil)
158-
err := json.Compact(out, []byte(d.Config.JSON))
205+
err := json.Compact(out, []byte(d.Config.JSONIETF))
159206
if err != nil {
160207
return nil, err
161208
}

0 commit comments

Comments
 (0)