| /* | |
| * 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 adaptercore | |
| import ( | |
| "reflect" | |
| "runtime" | |
| "github.com/opencord/voltha-go/common/log" | |
| ) | |
| // DeviceState OLT Device state | |
| type DeviceState int | |
| const ( | |
| // deviceStateNull OLT is not instantiated | |
| deviceStateNull DeviceState = iota | |
| // deviceStateInit OLT is instantiated | |
| deviceStateInit | |
| // deviceStateConnected Grpc session established with OLT | |
| deviceStateConnected | |
| // deviceStateUp Admin state of OLT is UP | |
| deviceStateUp | |
| // deviceStateDown Admin state of OLT is down | |
| deviceStateDown | |
| ) | |
| // Trigger for changing the state | |
| type Trigger int | |
| const ( | |
| // DeviceInit Go to Device init state | |
| DeviceInit Trigger = iota | |
| // GrpcConnected Go to connected state | |
| GrpcConnected | |
| // DeviceUpInd Go to Device up state | |
| DeviceUpInd | |
| // DeviceDownInd Go to Device down state | |
| DeviceDownInd | |
| // GrpcDisconnected Go to Device init state | |
| GrpcDisconnected | |
| ) | |
| // TransitionHandler function type for handling transition | |
| type TransitionHandler func() error | |
| // Transition to store state machine | |
| type Transition struct { | |
| previousState []DeviceState | |
| currentState DeviceState | |
| before []TransitionHandler | |
| after []TransitionHandler | |
| } | |
| // TransitionMap to store all the states and current device state | |
| type TransitionMap struct { | |
| transitions map[Trigger]Transition | |
| currentDeviceState DeviceState | |
| } | |
| // OpenoltDevice state machine: | |
| // | |
| // null ----> init ------> connected -----> up -----> down | |
| // ^ ^ | ^ | | | |
| // | | | | | | | |
| // | +-------------+ +---------+ | | |
| // | | | |
| // +-----------------------------------------+ | |
| // NewTransitionMap create a new state machine with all the transitions | |
| func NewTransitionMap(dh *DeviceHandler) *TransitionMap { | |
| var transitionMap TransitionMap | |
| transitionMap.currentDeviceState = deviceStateNull | |
| transitionMap.transitions = make(map[Trigger]Transition) | |
| // In doInit establish the grpc session | |
| transitionMap.transitions[DeviceInit] = | |
| Transition{ | |
| previousState: []DeviceState{deviceStateNull}, | |
| currentState: deviceStateInit, | |
| before: []TransitionHandler{dh.doStateInit}, | |
| after: []TransitionHandler{dh.postInit}} | |
| // If gRpc session fails, re-establish the grpc session | |
| transitionMap.transitions[GrpcDisconnected] = | |
| Transition{ | |
| previousState: []DeviceState{deviceStateConnected, deviceStateDown}, | |
| currentState: deviceStateInit, | |
| before: []TransitionHandler{dh.doStateInit}, | |
| after: []TransitionHandler{dh.postInit}} | |
| // in doConnected, create logical device and read the indications | |
| transitionMap.transitions[GrpcConnected] = | |
| Transition{ | |
| previousState: []DeviceState{deviceStateInit}, | |
| currentState: deviceStateConnected, | |
| before: []TransitionHandler{dh.doStateConnected}} | |
| // Once the olt UP is indication received, then do state up | |
| transitionMap.transitions[DeviceUpInd] = | |
| Transition{ | |
| previousState: []DeviceState{deviceStateConnected, deviceStateDown}, | |
| currentState: deviceStateUp, | |
| before: []TransitionHandler{dh.doStateUp}} | |
| // If olt DOWN indication comes then do sate down | |
| transitionMap.transitions[DeviceDownInd] = | |
| Transition{ | |
| previousState: []DeviceState{deviceStateUp}, | |
| currentState: deviceStateDown, | |
| before: []TransitionHandler{dh.doStateDown}} | |
| return &transitionMap | |
| } | |
| // funcName gets the handler function name | |
| func funcName(f interface{}) string { | |
| p := reflect.ValueOf(f).Pointer() | |
| rf := runtime.FuncForPC(p) | |
| return rf.Name() | |
| } | |
| // isValidTransition checks for the new state transition is valid from current state | |
| func (tMap *TransitionMap) isValidTransition(trigger Trigger) bool { | |
| // Validate the state transition | |
| for _, state := range tMap.transitions[trigger].previousState { | |
| if tMap.currentDeviceState == state { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| // Handle moves the state machine to next state based on the trigger and invokes the before and | |
| // after handlers | |
| func (tMap *TransitionMap) Handle(trigger Trigger) { | |
| // Check whether the transtion is valid from current state | |
| if !tMap.isValidTransition(trigger) { | |
| log.Errorw("Invalid transition triggered ", log.Fields{"CurrentState": tMap.currentDeviceState, "Trigger": trigger}) | |
| return | |
| } | |
| // Invoke the before handlers | |
| beforeHandlers := tMap.transitions[trigger].before | |
| if beforeHandlers != nil { | |
| for _, handler := range beforeHandlers { | |
| log.Debugw("running-before-handler", log.Fields{"handler": funcName(handler)}) | |
| if err := handler(); err != nil { | |
| // TODO handle error | |
| return | |
| } | |
| } | |
| } else { | |
| log.Debugw("No handlers for before", log.Fields{"trigger": trigger}) | |
| } | |
| // Update the state | |
| tMap.currentDeviceState = tMap.transitions[trigger].currentState | |
| log.Debugw("Updated device state ", log.Fields{"CurrentDeviceState": tMap.currentDeviceState}) | |
| // Invoke the after handlers | |
| afterHandlers := tMap.transitions[trigger].after | |
| if afterHandlers != nil { | |
| for _, handler := range afterHandlers { | |
| log.Debugw("running-after-handler", log.Fields{"handler": funcName(handler)}) | |
| if err := handler(); err != nil { | |
| // TODO handle error | |
| return | |
| } | |
| } | |
| } else { | |
| log.Debugw("No handlers for after", log.Fields{"trigger": trigger}) | |
| } | |
| } |