| /* |
| * Copyright 2019-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 adapter |
| |
| import ( |
| "context" |
| "errors" |
| "fmt" |
| "sync" |
| "time" |
| |
| "github.com/opencord/voltha-lib-go/v7/pkg/db" |
| vgrpc "github.com/opencord/voltha-lib-go/v7/pkg/grpc" |
| "github.com/opencord/voltha-protos/v5/go/adapter_service" |
| "github.com/opencord/voltha-protos/v5/go/common" |
| "github.com/opencord/voltha-protos/v5/go/core_adapter" |
| |
| "github.com/gogo/protobuf/proto" |
| "github.com/golang/protobuf/ptypes/empty" |
| "github.com/opencord/voltha-go/db/model" |
| "github.com/opencord/voltha-lib-go/v7/pkg/log" |
| "github.com/opencord/voltha-lib-go/v7/pkg/probe" |
| "github.com/opencord/voltha-protos/v5/go/voltha" |
| "google.golang.org/grpc/codes" |
| "google.golang.org/grpc/status" |
| ) |
| |
| // Manager represents adapter manager attributes |
| type Manager struct { |
| adapterAgents map[string]*agent |
| adapterEndpoints map[Endpoint]*agent |
| deviceTypes map[string]*voltha.DeviceType |
| adapterDbProxy *model.Proxy |
| deviceTypeDbProxy *model.Proxy |
| onAdapterRestart vgrpc.RestartedHandler |
| endpointMgr EndpointManager |
| lockAdapterAgentsMap sync.RWMutex |
| lockDeviceTypesMap sync.RWMutex |
| lockAdapterEndPointsMap sync.RWMutex |
| liveProbeInterval time.Duration |
| coreEndpoint string |
| } |
| |
| // SetAdapterRestartedCallback is used to set the callback that needs to be invoked on an adapter restart |
| func (aMgr *Manager) SetAdapterRestartedCallback(onAdapterRestart vgrpc.RestartedHandler) { |
| aMgr.onAdapterRestart = onAdapterRestart |
| } |
| |
| func NewAdapterManager( |
| coreEndpoint string, |
| dbPath *model.Path, |
| coreInstanceID string, |
| backend *db.Backend, |
| liveProbeInterval time.Duration, |
| ) *Manager { |
| return &Manager{ |
| adapterDbProxy: dbPath.Proxy("adapters"), |
| deviceTypeDbProxy: dbPath.Proxy("device_types"), |
| deviceTypes: make(map[string]*voltha.DeviceType), |
| adapterAgents: make(map[string]*agent), |
| adapterEndpoints: make(map[Endpoint]*agent), |
| endpointMgr: NewEndpointManager(backend), |
| liveProbeInterval: liveProbeInterval, |
| coreEndpoint: coreEndpoint, |
| } |
| } |
| |
| func (aMgr *Manager) Start(ctx context.Context, serviceName string) { |
| probe.UpdateStatusFromContext(ctx, serviceName, probe.ServiceStatusPreparing) |
| logger.Infow(ctx, "starting-service", log.Fields{"service": serviceName}) |
| |
| // Load the existing adapterAgents and device types - this will also ensure the correct paths have been |
| // created if there are no data in the dB to start |
| err := aMgr.loadAdaptersAndDevicetypesInMemory(ctx) |
| if err != nil { |
| logger.Fatalw(ctx, "failed-to-load-adapters-and-device-types-in-memory", log.Fields{"service": serviceName, "error": err}) |
| } |
| |
| probe.UpdateStatusFromContext(ctx, serviceName, probe.ServiceStatusRunning) |
| logger.Infow(ctx, "service-started", log.Fields{"service": serviceName}) |
| } |
| |
| func (aMgr *Manager) Stop(ctx context.Context) { |
| // Stop all adapters |
| aMgr.lockAdapterAgentsMap.RLock() |
| defer aMgr.lockAdapterAgentsMap.RUnlock() |
| var wg sync.WaitGroup |
| for _, adapterAgent := range aMgr.adapterAgents { |
| // Run the agent stop in its own go routine to notify to the |
| // adapters that the Core is no longer a client |
| wg.Add(1) |
| go func(agt *agent) { |
| agt.stop(ctx) |
| wg.Done() |
| }(adapterAgent) |
| } |
| // Wait for all tests to complete |
| wg.Wait() |
| } |
| |
| func (aMgr *Manager) GetAdapterEndpoint(ctx context.Context, deviceID string, deviceType string) (string, error) { |
| endPoint, err := aMgr.endpointMgr.GetEndpoint(ctx, deviceID, deviceType) |
| if err != nil { |
| return "", err |
| } |
| return string(endPoint), nil |
| } |
| |
| func (aMgr *Manager) GetAdapterWithEndpoint(ctx context.Context, endPoint string) (*voltha.Adapter, error) { |
| aMgr.lockAdapterEndPointsMap.RLock() |
| agent, have := aMgr.adapterEndpoints[Endpoint(endPoint)] |
| aMgr.lockAdapterEndPointsMap.RUnlock() |
| |
| if have { |
| return agent.getAdapter(ctx), nil |
| } |
| |
| return nil, errors.New("Not found") |
| } |
| |
| func (aMgr *Manager) GetAdapterNameWithEndpoint(ctx context.Context, endPoint string) (string, error) { |
| aMgr.lockAdapterEndPointsMap.RLock() |
| agent, have := aMgr.adapterEndpoints[Endpoint(endPoint)] |
| aMgr.lockAdapterEndPointsMap.RUnlock() |
| |
| if have { |
| return agent.adapter.Id, nil |
| } |
| |
| return "", errors.New("Not found") |
| } |
| |
| func (aMgr *Manager) GetAdapterClient(_ context.Context, endpoint string) (adapter_service.AdapterServiceClient, error) { |
| if endpoint == "" { |
| return nil, errors.New("endpoint-cannot-be-empty") |
| } |
| aMgr.lockAdapterEndPointsMap.RLock() |
| defer aMgr.lockAdapterEndPointsMap.RUnlock() |
| |
| if agent, have := aMgr.adapterEndpoints[Endpoint(endpoint)]; have { |
| return agent.getClient() |
| } |
| |
| return nil, fmt.Errorf("Endpoint-not-found-%s", endpoint) |
| } |
| |
| func (aMgr *Manager) addAdapter(ctx context.Context, adapter *voltha.Adapter, saveToDb bool) error { |
| aMgr.lockAdapterAgentsMap.Lock() |
| aMgr.lockAdapterEndPointsMap.Lock() |
| defer aMgr.lockAdapterEndPointsMap.Unlock() |
| defer aMgr.lockAdapterAgentsMap.Unlock() |
| logger.Debugw(ctx, "adding-adapter", log.Fields{"adapterId": adapter.Id, "vendor": adapter.Vendor, |
| "currentReplica": adapter.CurrentReplica, "totalReplicas": adapter.TotalReplicas, "endpoint": adapter.Endpoint}) |
| if _, exist := aMgr.adapterAgents[adapter.Id]; !exist { |
| if saveToDb { |
| // Save the adapter to the KV store - first check if it already exist |
| if have, err := aMgr.adapterDbProxy.Get(log.WithSpanFromContext(context.Background(), ctx), adapter.Id, &voltha.Adapter{}); err != nil { |
| logger.Errorw(ctx, "failed-to-get-adapters-from-cluster-proxy", log.Fields{"error": err}) |
| return err |
| } else if !have { |
| if err := aMgr.adapterDbProxy.Set(log.WithSpanFromContext(context.Background(), ctx), adapter.Id, adapter); err != nil { |
| logger.Errorw(ctx, "failed-to-save-adapter", log.Fields{"adapterId": adapter.Id, "vendor": adapter.Vendor, |
| "currentReplica": adapter.CurrentReplica, "totalReplicas": adapter.TotalReplicas, "endpoint": adapter.Endpoint, "replica": adapter.CurrentReplica, "total": adapter.TotalReplicas}) |
| return err |
| } |
| logger.Debugw(ctx, "adapter-saved-to-KV-Store", log.Fields{"adapterId": adapter.Id, "vendor": adapter.Vendor, |
| "currentReplica": adapter.CurrentReplica, "totalReplicas": adapter.TotalReplicas, "endpoint": adapter.Endpoint, "replica": adapter.CurrentReplica, "total": adapter.TotalReplicas}) |
| } else { |
| logger.Warnw(ctx, "adding-adapter-already-in-KV-store", log.Fields{ |
| "adapterName": adapter.Id, |
| "adapterReplica": adapter.CurrentReplica, |
| }) |
| } |
| } |
| clonedAdapter := (proto.Clone(adapter)).(*voltha.Adapter) |
| // Use a muted adapter restart handler which is invoked by the corresponding gRPC client on an adapter restart. |
| // This handler just log the restart event. The actual action taken following an adapter restart |
| // will be done when an adapter re-registers itself. |
| aMgr.adapterAgents[adapter.Id] = newAdapterAgent(aMgr.coreEndpoint, clonedAdapter, aMgr.mutedAdapterRestartedHandler, aMgr.liveProbeInterval) |
| aMgr.adapterEndpoints[Endpoint(adapter.Endpoint)] = aMgr.adapterAgents[adapter.Id] |
| } |
| return nil |
| } |
| |
| func (aMgr *Manager) addDeviceTypes(ctx context.Context, deviceTypes *voltha.DeviceTypes, saveToDb bool) error { |
| if deviceTypes == nil { |
| return fmt.Errorf("no-device-type") |
| } |
| logger.Debugw(ctx, "adding-device-types", log.Fields{"deviceTypes": deviceTypes}) |
| aMgr.lockAdapterAgentsMap.Lock() |
| defer aMgr.lockAdapterAgentsMap.Unlock() |
| aMgr.lockDeviceTypesMap.Lock() |
| defer aMgr.lockDeviceTypesMap.Unlock() |
| |
| // create an in memory map to fetch the entire voltha.DeviceType from a device.Type string |
| for _, deviceType := range deviceTypes.Items { |
| aMgr.deviceTypes[deviceType.Id] = deviceType |
| } |
| |
| if saveToDb { |
| // Save the device types to the KV store |
| for _, deviceType := range deviceTypes.Items { |
| if have, err := aMgr.deviceTypeDbProxy.Get(log.WithSpanFromContext(context.Background(), ctx), deviceType.Id, &voltha.DeviceType{}); err != nil { |
| logger.Errorw(ctx, "Failed-to--device-types-from-cluster-data-proxy", log.Fields{"error": err}) |
| return err |
| } else if !have { |
| // Does not exist - save it |
| clonedDType := (proto.Clone(deviceType)).(*voltha.DeviceType) |
| if err := aMgr.deviceTypeDbProxy.Set(log.WithSpanFromContext(context.Background(), ctx), deviceType.Id, clonedDType); err != nil { |
| logger.Errorw(ctx, "Failed-to-add-device-types-to-cluster-data-proxy", log.Fields{"error": err}) |
| return err |
| } |
| logger.Debugw(ctx, "device-type-saved-to-KV-Store", log.Fields{"deviceType": deviceType}) |
| } |
| } |
| } |
| return nil |
| } |
| |
| //loadAdaptersAndDevicetypesInMemory loads the existing set of adapters and device types in memory |
| func (aMgr *Manager) loadAdaptersAndDevicetypesInMemory(ctx context.Context) error { |
| // Load the adapters |
| var adapters []*voltha.Adapter |
| if err := aMgr.adapterDbProxy.List(log.WithSpanFromContext(context.Background(), ctx), &adapters); err != nil { |
| logger.Errorw(ctx, "Failed-to-list-adapters-from-cluster-data-proxy", log.Fields{"error": err}) |
| return err |
| } |
| |
| logger.Debugw(ctx, "retrieved-adapters", log.Fields{"count": len(adapters)}) |
| |
| if len(adapters) != 0 { |
| for _, adapter := range adapters { |
| if err := aMgr.addAdapter(ctx, adapter, false); err != nil { |
| logger.Errorw(ctx, "failed-to-add-adapter", log.Fields{"adapterId": adapter.Id}) |
| } else { |
| logger.Debugw(ctx, "adapter-added-successfully", log.Fields{"adapterId": adapter.Id}) |
| } |
| } |
| } |
| |
| // Load the device types |
| var deviceTypes []*voltha.DeviceType |
| if err := aMgr.deviceTypeDbProxy.List(log.WithSpanFromContext(context.Background(), ctx), &deviceTypes); err != nil { |
| logger.Errorw(ctx, "Failed-to-list-device-types-from-cluster-data-proxy", log.Fields{"error": err}) |
| return err |
| } |
| |
| logger.Debugw(ctx, "retrieved-devicetypes", log.Fields{"count": len(deviceTypes)}) |
| |
| if len(deviceTypes) != 0 { |
| dTypes := &voltha.DeviceTypes{Items: []*voltha.DeviceType{}} |
| for _, dType := range deviceTypes { |
| logger.Debugw(ctx, "found-existing-device-types", log.Fields{"deviceTypes": deviceTypes}) |
| dTypes.Items = append(dTypes.Items, dType) |
| } |
| if err := aMgr.addDeviceTypes(ctx, dTypes, false); err != nil { |
| logger.Errorw(ctx, "failed-to-add-device-type", log.Fields{"deviceTypes": deviceTypes}) |
| } else { |
| logger.Debugw(ctx, "device-type-added-successfully", log.Fields{"deviceTypes": deviceTypes}) |
| } |
| } |
| |
| // Start the adapter agents - this will trigger the connection to the adapter |
| aMgr.lockAdapterAgentsMap.RLock() |
| defer aMgr.lockAdapterAgentsMap.RUnlock() |
| for _, adapterAgent := range aMgr.adapterAgents { |
| subCtx := log.WithSpanFromContext(context.Background(), ctx) |
| if err := adapterAgent.start(subCtx); err != nil { |
| logger.Errorw(ctx, "failed-to-start-adapter", log.Fields{"adapter-endpoint": adapterAgent.adapterAPIEndPoint}) |
| } |
| } |
| |
| logger.Debug(ctx, "no-existing-device-type-found") |
| |
| return nil |
| } |
| |
| func (aMgr *Manager) RegisterAdapter(ctx context.Context, registration *core_adapter.AdapterRegistration) (*empty.Empty, error) { |
| adapter := registration.Adapter |
| deviceTypes := registration.DTypes |
| logger.Infow(ctx, "RegisterAdapter", log.Fields{"adapter": adapter, "deviceTypes": deviceTypes.Items}) |
| |
| if adapter.Type == "" { |
| logger.Errorw(ctx, "adapter-not-specifying-type", log.Fields{ |
| "adapterId": adapter.Id, |
| "vendor": adapter.Vendor, |
| "type": adapter.Type, |
| }) |
| return nil, status.Error(codes.InvalidArgument, "adapter-not-specifying-type") |
| } |
| |
| if adpt, _ := aMgr.getAdapter(ctx, adapter.Id); adpt != nil { |
| // Already registered - Adapter may have restarted. Trigger the reconcile process for that adapter |
| logger.Warnw(ctx, "adapter-restarted", log.Fields{"adapter": adpt.Id, "endpoint": adpt.Endpoint}) |
| |
| // First reset the adapter connection |
| agt, err := aMgr.getAgent(ctx, adpt.Id) |
| if err != nil { |
| logger.Errorw(ctx, "no-adapter-agent", log.Fields{"error": err}) |
| return nil, err |
| } |
| agt.resetConnection(ctx) |
| |
| go func() { |
| err := aMgr.onAdapterRestart(log.WithSpanFromContext(context.Background(), ctx), adpt.Endpoint) |
| if err != nil { |
| logger.Errorw(ctx, "unable-to-restart-adapter", log.Fields{"error": err}) |
| } |
| }() |
| return &empty.Empty{}, nil |
| } |
| // Save the adapter and the device types |
| if err := aMgr.addAdapter(ctx, adapter, true); err != nil { |
| logger.Errorw(ctx, "failed-to-add-adapter", log.Fields{"error": err}) |
| return nil, err |
| } |
| if err := aMgr.addDeviceTypes(ctx, deviceTypes, true); err != nil { |
| logger.Errorw(ctx, "failed-to-add-device-types", log.Fields{"error": err}) |
| return nil, err |
| } |
| |
| logger.Debugw(ctx, "adapter-registered", log.Fields{"adapterId": adapter.Id, "vendor": adapter.Vendor, |
| "currentReplica": adapter.CurrentReplica, "totalReplicas": adapter.TotalReplicas, "endpoint": adapter.Endpoint}) |
| |
| // Setup the endpoints for this adapter |
| if err := aMgr.endpointMgr.RegisterAdapter(ctx, adapter, deviceTypes); err != nil { |
| logger.Errorw(ctx, "failed-to-register-adapter", log.Fields{"error": err}) |
| } |
| |
| // Start adapter instance - this will trigger the connection to the adapter |
| if agent, err := aMgr.getAgent(ctx, adapter.Id); agent != nil { |
| subCtx := log.WithSpanFromContext(context.Background(), ctx) |
| if err := agent.start(subCtx); err != nil { |
| logger.Errorw(ctx, "failed-to-start-adapter", log.Fields{"error": err}) |
| return nil, err |
| } |
| } else { |
| logger.Fatalw(ctx, "adapter-absent", log.Fields{"error": err, "adapter": adapter.Id}) |
| } |
| |
| return &empty.Empty{}, nil |
| } |
| |
| func (aMgr *Manager) GetAdapterTypeByVendorID(vendorID string) (string, error) { |
| aMgr.lockDeviceTypesMap.RLock() |
| defer aMgr.lockDeviceTypesMap.RUnlock() |
| for _, dType := range aMgr.deviceTypes { |
| for _, v := range dType.VendorIds { |
| if v == vendorID { |
| return dType.AdapterType, nil |
| } |
| } |
| } |
| return "", fmt.Errorf("vendor id %s not found", vendorID) |
| } |
| |
| // GetAdapterType returns the name of the device adapter that services this device type |
| func (aMgr *Manager) GetAdapterType(deviceType string) (string, error) { |
| aMgr.lockDeviceTypesMap.Lock() |
| defer aMgr.lockDeviceTypesMap.Unlock() |
| for _, dt := range aMgr.deviceTypes { |
| if deviceType == dt.Id { |
| return dt.AdapterType, nil |
| } |
| } |
| return "", fmt.Errorf("adapter-not-registered-for-device-type %s", deviceType) |
| } |
| |
| // ListDeviceTypes returns all the device types known to the system |
| func (aMgr *Manager) ListDeviceTypes(ctx context.Context, _ *empty.Empty) (*voltha.DeviceTypes, error) { |
| logger.Debug(ctx, "ListDeviceTypes") |
| aMgr.lockDeviceTypesMap.Lock() |
| defer aMgr.lockDeviceTypesMap.Unlock() |
| |
| deviceTypes := make([]*voltha.DeviceType, 0, len(aMgr.deviceTypes)) |
| for _, deviceType := range aMgr.deviceTypes { |
| deviceTypes = append(deviceTypes, deviceType) |
| } |
| return &voltha.DeviceTypes{Items: deviceTypes}, nil |
| } |
| |
| // GetDeviceType returns the device type proto definition given the name of the device type |
| func (aMgr *Manager) GetDeviceType(ctx context.Context, deviceType *common.ID) (*voltha.DeviceType, error) { |
| logger.Debugw(ctx, "GetDeviceType", log.Fields{"typeid": deviceType.Id}) |
| aMgr.lockDeviceTypesMap.Lock() |
| defer aMgr.lockDeviceTypesMap.Unlock() |
| |
| dType, exist := aMgr.deviceTypes[deviceType.Id] |
| if !exist { |
| return nil, status.Errorf(codes.NotFound, "device_type-%s", deviceType.Id) |
| } |
| return dType, nil |
| } |
| |
| // ListAdapters returns the contents of all adapters known to the system |
| func (aMgr *Manager) ListAdapters(ctx context.Context, _ *empty.Empty) (*voltha.Adapters, error) { |
| logger.Debug(ctx, "Listing adapters") |
| result := &voltha.Adapters{Items: []*voltha.Adapter{}} |
| aMgr.lockAdapterAgentsMap.RLock() |
| defer aMgr.lockAdapterAgentsMap.RUnlock() |
| for _, adapterAgent := range aMgr.adapterAgents { |
| if a := adapterAgent.getAdapter(ctx); a != nil { |
| result.Items = append(result.Items, (proto.Clone(a)).(*voltha.Adapter)) |
| } |
| } |
| logger.Debugw(ctx, "Listing adapters", log.Fields{"result": result}) |
| return result, nil |
| } |
| |
| func (aMgr *Manager) getAgent(ctx context.Context, adapterID string) (*agent, error) { |
| aMgr.lockAdapterAgentsMap.RLock() |
| defer aMgr.lockAdapterAgentsMap.RUnlock() |
| if adapterAgent, ok := aMgr.adapterAgents[adapterID]; ok { |
| return adapterAgent, nil |
| } |
| return nil, errors.New("Not found") |
| } |
| |
| func (aMgr *Manager) getAdapter(ctx context.Context, adapterID string) (*voltha.Adapter, error) { |
| aMgr.lockAdapterAgentsMap.RLock() |
| defer aMgr.lockAdapterAgentsMap.RUnlock() |
| if adapterAgent, ok := aMgr.adapterAgents[adapterID]; ok { |
| return adapterAgent.getAdapter(ctx), nil |
| } |
| return nil, errors.New("Not found") |
| } |
| |
| // mutedAdapterRestartedHandler will be invoked by the grpc client on an adapter restart. |
| // Since the Adapter will re-register itself and that will trigger the reconcile process, |
| // therefore this handler does nothing, other than logging the event. |
| func (aMgr *Manager) mutedAdapterRestartedHandler(ctx context.Context, endpoint string) error { |
| logger.Infow(ctx, "muted-adapter-restarted", log.Fields{"endpoint": endpoint}) |
| return nil |
| } |
| |
| func (aMgr *Manager) WaitUntilConnectionsToAdaptersAreUp(ctx context.Context, connectionRetryInterval time.Duration) error { |
| logger.Infow(ctx, "waiting-for-adapters-to-be-up", log.Fields{"retry-interval": connectionRetryInterval}) |
| for { |
| aMgr.lockAdapterAgentsMap.Lock() |
| numAdapters := len(aMgr.adapterAgents) |
| if numAdapters == 0 { |
| // No adapter registered yet |
| aMgr.lockAdapterAgentsMap.Unlock() |
| logger.Info(ctx, "no-adapter-registered") |
| return nil |
| } |
| // A case of Core restart |
| agentsUp := true |
| adapterloop: |
| for _, agt := range aMgr.adapterAgents { |
| agentsUp = agentsUp && agt.IsConnectionUp() |
| if !agentsUp { |
| break adapterloop |
| } |
| } |
| aMgr.lockAdapterAgentsMap.Unlock() |
| if agentsUp { |
| logger.Infow(ctx, "adapter-connections-ready", log.Fields{"adapter-count": numAdapters}) |
| return nil |
| } |
| logger.Warnw(ctx, "adapter-connections-not-ready", log.Fields{"adapter-count": numAdapters}) |
| select { |
| case <-time.After(connectionRetryInterval): |
| logger.Infow(ctx, "retrying-adapter-connections", log.Fields{"adapter-count": numAdapters}) |
| continue |
| case <-ctx.Done(): |
| logger.Errorw(ctx, "context-timeout", log.Fields{"adapter-count": numAdapters, "err": ctx.Err()}) |
| return ctx.Err() |
| } |
| } |
| } |