[VOL-3678] First implementation of the BBSim-sadis-server
Change-Id: I5077a8f861f4cc6af9759f31a4a415042c05eba3
diff --git a/internal/core/common.go b/internal/core/common.go
new file mode 100644
index 0000000..0d360ad
--- /dev/null
+++ b/internal/core/common.go
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package core
+
+import "github.com/opencord/voltha-lib-go/v4/pkg/log"
+
+var logger log.CLogger
+
+func SetupLogger(logLevel log.LogLevel, logFormat string) {
+ // Setup this package so that it's log level can be modified at run time
+ var err error
+ logger, err = log.RegisterPackage(logFormat, logLevel, log.Fields{})
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/internal/core/sadis_if.go b/internal/core/sadis_if.go
new file mode 100644
index 0000000..3b5abfb
--- /dev/null
+++ b/internal/core/sadis_if.go
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package core
+
+// TODO this should be imported from github.com/opencord/bbsim
+// but to do that we need to move them from the "internal" module
+
+type SadisConfig struct {
+ Sadis SadisEntries `json:"sadis"`
+ BandwidthProfile BandwidthProfileEntries `json:"bandwidthprofile"`
+}
+
+type SadisEntries struct {
+ Integration SadisIntegration `json:"integration"`
+ Entries []*SadisEntry `json:"entries,omitempty"`
+ //Entries []interface{} `json:"entries,omitempty"`
+}
+
+type BandwidthProfileEntries struct {
+ Integration SadisIntegration `json:"integration"`
+ Entries []*SadisBWPEntry `json:"entries,omitempty"`
+}
+
+type SadisIntegration struct {
+ URL string `json:"url,omitempty"`
+ Cache struct {
+ Enabled bool `json:"enabled"`
+ MaxSize int `json:"maxsize"`
+ TTL string `json:"ttl"`
+ } `json:"cache"`
+}
+
+type SadisEntry struct {
+ // common
+ ID string `json:"id"`
+ // olt
+ HardwareIdentifier string `json:"hardwareIdentifier"`
+ IPAddress string `json:"ipAddress"`
+ NasID string `json:"nasId"`
+ UplinkPort int `json:"uplinkPort"`
+ // onu
+ NasPortID string `json:"nasPortId"`
+ CircuitID string `json:"circuitId"`
+ RemoteID string `json:"remoteId"`
+ UniTagList []SadisUniTag `json:"uniTagList"`
+}
+
+type SadisOltEntry struct {
+ ID string `json:"id"`
+ HardwareIdentifier string `json:"hardwareIdentifier"`
+ IPAddress string `json:"ipAddress"`
+ NasID string `json:"nasId"`
+ UplinkPort int `json:"uplinkPort"`
+}
+
+type SadisOnuEntryV2 struct {
+ ID string `json:"id"`
+ NasPortID string `json:"nasPortId"`
+ CircuitID string `json:"circuitId"`
+ RemoteID string `json:"remoteId"`
+ UniTagList []SadisUniTag `json:"uniTagList"` // this can be SadisUniTagAtt, SadisUniTagDt
+}
+
+type SadisUniTag struct {
+ UniTagMatch int `json:"uniTagMatch,omitempty"`
+ PonCTag int `json:"ponCTag,omitempty"`
+ PonSTag int `json:"ponSTag,omitempty"`
+ TechnologyProfileID int `json:"technologyProfileId,omitempty"`
+ UpstreamBandwidthProfile string `json:"upstreamBandwidthProfile,omitempty"`
+ DownstreamBandwidthProfile string `json:"downstreamBandwidthProfile,omitempty"`
+ IsDhcpRequired bool `json:"isDhcpRequired,omitempty"`
+ IsIgmpRequired bool `json:"isIgmpRequired,omitempty"`
+ ConfiguredMacAddress string `json:"configuredMacAddress,omitempty"`
+ UsPonCTagPriority uint8 `json:"usPonCTagPriority,omitempty"`
+ UsPonSTagPriority uint8 `json:"usPonSTagPriority,omitempty"`
+ DsPonCTagPriority uint8 `json:"dsPonCTagPriority,omitempty"`
+ DsPonSTagPriority uint8 `json:"dsPonSTagPriority,omitempty"`
+ ServiceName string `json:"serviceName,omitempty"`
+}
+
+// SADIS BandwithProfile Entry
+type SadisBWPEntry struct {
+ ID string `json:"id"`
+ AIR int `json:"air"`
+ CBS int `json:"cbs"`
+ CIR int `json:"cir"`
+ EBS int `json:"ebs"`
+ EIR int `json:"eir"`
+}
diff --git a/internal/core/server.go b/internal/core/server.go
new file mode 100644
index 0000000..8a5dfd6
--- /dev/null
+++ b/internal/core/server.go
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package core
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "github.com/gorilla/mux"
+ "github.com/opencord/voltha-lib-go/v4/pkg/log"
+ "net/http"
+ "sync"
+)
+
+type Server struct {
+ store *Store
+}
+
+func NewServer(store *Store) *Server {
+ return &Server{
+ store: store,
+ }
+}
+
+func (s *Server) StartSadisServer(wg *sync.WaitGroup) {
+ defer wg.Done()
+ ctx := context.Background()
+
+ addr := "0.0.0.0:8080"
+
+ router := mux.NewRouter().StrictSlash(true)
+ router.HandleFunc("/subscribers/{ID}", s.serveEntry)
+ router.HandleFunc("/profiles/{ID}", s.serveBWPEntry)
+
+ logger.Fatal(ctx, http.ListenAndServe(addr, router))
+}
+
+func (s Server) serveEntry(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ id := vars["ID"]
+
+ ctx := context.TODO()
+ logger.Debugw(ctx, "received-sadis-entry-request", log.Fields{"id": id})
+
+ w.Header().Set("Content-Type", "application/json")
+
+ if olt, err := s.store.getOlt(r.Context(), id); err == nil {
+ w.WriteHeader(http.StatusOK)
+ _ = json.NewEncoder(w).Encode(olt)
+ logger.Infow(ctx, "responded-to-sadis-olt-entry-request", log.Fields{"id": id})
+ return
+ }
+
+ if onu, err := s.store.getOnu(r.Context(), id); err == nil {
+ w.WriteHeader(http.StatusOK)
+ _ = json.NewEncoder(w).Encode(onu)
+ logger.Infow(ctx, "responded-to-sadis-onu-entry-request", log.Fields{"id": id})
+ return
+ }
+
+ w.WriteHeader(http.StatusNotFound)
+ msg := make(map[string]interface{})
+ msg["statusCode"] = http.StatusNotFound
+ msg["message"] = fmt.Sprintf("Entry with ID %s not found.", id)
+ _ = json.NewEncoder(w).Encode(msg)
+
+ logger.Warnw(ctx, "sadis-entry-not-found", log.Fields{"id": id})
+}
+
+func (s Server) serveBWPEntry(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ id := vars["ID"]
+
+ ctx := context.TODO()
+ logger.Debugw(ctx, "received-sadis-bandwidthprofile-request", log.Fields{"id": id})
+
+ w.Header().Set("Content-Type", "application/json")
+
+ if bp, err := s.store.getBp(r.Context(), id); err == nil {
+ w.WriteHeader(http.StatusOK)
+ _ = json.NewEncoder(w).Encode(bp)
+ logger.Infow(ctx, "responded-to-sadis-bandwidthprofile-request", log.Fields{"id": id})
+ return
+ }
+
+ w.WriteHeader(http.StatusNotFound)
+ msg := make(map[string]interface{})
+ msg["statusCode"] = http.StatusNotFound
+ msg["message"] = fmt.Sprintf("BandwidthProfile with ID %s not found.", id)
+ _ = json.NewEncoder(w).Encode(msg)
+
+ logger.Warnw(ctx, "sadis-bandwidthprofile-not-found", log.Fields{"id": id})
+}
diff --git a/internal/core/store.go b/internal/core/store.go
new file mode 100644
index 0000000..a6d24fb
--- /dev/null
+++ b/internal/core/store.go
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package core
+
+import (
+ "context"
+ "fmt"
+ "github.com/opencord/voltha-lib-go/v4/pkg/log"
+ "sync"
+)
+
+type Store struct {
+ olts sync.Map
+ onus sync.Map
+ bps sync.Map
+}
+
+func NewStore() *Store {
+ return &Store{
+ olts: sync.Map{},
+ onus: sync.Map{},
+ bps: sync.Map{},
+ }
+}
+
+func (s *Store) addOlt(ctx context.Context, entry SadisOltEntry) {
+ logger.Debugw(ctx, "adding-olt", log.Fields{"olt": entry})
+ s.olts.Store(entry.ID, entry)
+}
+
+func (s *Store) addOnu(ctx context.Context, entry SadisOnuEntryV2) {
+ logger.Debugw(ctx, "adding-onu", log.Fields{"onu": entry})
+ s.onus.Store(entry.ID, entry)
+}
+
+func (s *Store) addBp(ctx context.Context, entry SadisBWPEntry) {
+ logger.Debugw(ctx, "adding-bp", log.Fields{"bp": entry})
+ s.bps.Store(entry.ID, entry)
+}
+
+func (s *Store) getOlt(ctx context.Context, id string) (*SadisOltEntry, error) {
+ logger.Debugw(ctx, "getting-olt", log.Fields{"olt": id})
+ if entry, ok := s.olts.Load(id); ok {
+ e := entry.(SadisOltEntry)
+ return &e, nil
+ }
+ return nil, fmt.Errorf("olt-not-found-in-store")
+}
+
+func (s *Store) getOnu(ctx context.Context, id string) (*SadisOnuEntryV2, error) {
+ logger.Debugw(ctx, "getting-onu", log.Fields{"onu": id})
+ if entry, ok := s.onus.Load(id); ok {
+ e := entry.(SadisOnuEntryV2)
+ return &e, nil
+ }
+ return nil, fmt.Errorf("onu-not-found-in-store")
+}
+
+func (s *Store) getBp(ctx context.Context, id string) (*SadisBWPEntry, error) {
+ logger.Debugw(ctx, "getting-bp", log.Fields{"bp": id})
+ if entry, ok := s.bps.Load(id); ok {
+ e := entry.(SadisBWPEntry)
+ return &e, nil
+ }
+ return nil, fmt.Errorf("bp-not-found-in-store")
+}
diff --git a/internal/core/watcher.go b/internal/core/watcher.go
new file mode 100644
index 0000000..99e75d4
--- /dev/null
+++ b/internal/core/watcher.go
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2020-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package core
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "github.com/opencord/bbsim-sadis-server/internal/utils"
+ "github.com/opencord/voltha-lib-go/v4/pkg/log"
+ v1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes"
+ "net/http"
+ "sync"
+ "time"
+)
+
+type Watcher struct {
+ client *kubernetes.Clientset
+ store *Store
+ config *utils.ConfigFlags
+}
+
+func NewWatcher(client *kubernetes.Clientset, store *Store, cf *utils.ConfigFlags) *Watcher {
+ return &Watcher{
+ client: client,
+ store: store,
+ config: cf,
+ }
+}
+
+func (w *Watcher) Watch(ctx context.Context, wg *sync.WaitGroup) {
+ defer wg.Done()
+ for {
+ logger.Debug(ctx, "fetching-pods")
+ services, err := w.client.CoreV1().Services("").List(context.TODO(), metav1.ListOptions{LabelSelector: "app=bbsim"})
+ if err != nil {
+ panic(err.Error())
+ }
+ logger.Debugf(ctx, "There are %d services in the cluster", len(services.Items))
+ w.handleServices(ctx, services)
+ time.Sleep(10 * time.Second)
+ }
+}
+
+func (w *Watcher) handleServices(ctx context.Context, services *v1.ServiceList) {
+ // TODO if a service is removed we'll want to remove the related entries
+ for _, service := range services.Items {
+ if err := w.queryService(ctx, service); err != nil {
+ logger.Errorw(ctx, "error-while-reading-from-service", log.Fields{"error": err.Error()})
+ }
+ }
+}
+
+func (w *Watcher) queryService(ctx context.Context, service v1.Service) error {
+ endpoint := fmt.Sprintf("%s.%s.svc:%d", service.Name, service.Namespace, w.config.BBsimSadisPort)
+ logger.Infow(ctx, "querying-service", log.Fields{"endpoint": endpoint})
+
+ res, err := http.Get(fmt.Sprintf("http://%s/v2/static", endpoint))
+
+ if err != nil {
+ return err
+ }
+
+ if res.Body != nil {
+ defer res.Body.Close()
+ }
+
+ var result SadisConfig
+
+ decoder := json.NewDecoder(res.Body)
+ if err := decoder.Decode(&result); err != nil {
+ logger.Errorw(ctx, "cannot-decode-sadis-response", log.Fields{"error": err.Error()})
+ return err
+ }
+
+ logger.Debugw(ctx, "fetched-sadis-config", log.Fields{
+ "endpoint": endpoint,
+ "entries": len(result.Sadis.Entries),
+ "bandwidthProfiles": len(result.BandwidthProfile.Entries),
+ })
+
+ //for _, entry := range result.Sadis.Entries {
+ // switch entry.(type) {
+ // case SadisOltEntry:
+ // case *SadisOltEntry:
+ // logger.Infow(ctx, "olt-entry", log.Fields{"entry": entry})
+ // case SadisOnuEntryV2:
+ // case *SadisOnuEntryV2:
+ // logger.Infow(ctx, "onu-entry", log.Fields{"entry": entry})
+ // default:
+ // logger.Warnw(ctx, "unknown-entity", log.Fields{"entry": entry})
+ // }
+ //}
+
+ for _, entry := range result.Sadis.Entries {
+ if entry.HardwareIdentifier != "" {
+ e := SadisOltEntry{
+ ID: entry.ID,
+ HardwareIdentifier: entry.HardwareIdentifier,
+ IPAddress: entry.IPAddress,
+ NasID: entry.NasID,
+ UplinkPort: entry.UplinkPort,
+ }
+ w.store.addOlt(ctx, e)
+ continue
+ }
+ if len(entry.UniTagList) != 0 {
+ e := SadisOnuEntryV2{
+ ID: entry.ID,
+ NasPortID: entry.NasPortID,
+ CircuitID: entry.CircuitID,
+ RemoteID: entry.RemoteID,
+ UniTagList: entry.UniTagList,
+ }
+ w.store.addOnu(ctx, e)
+ continue
+ }
+ logger.Warnw(ctx, "unknown-entity", log.Fields{"entry": entry})
+ }
+
+ for _, bp := range result.BandwidthProfile.Entries {
+ w.store.addBp(ctx, *bp)
+ }
+
+ logger.Infow(ctx, "stored-sadis-config", log.Fields{"endpoint": endpoint})
+
+ return nil
+}