Girish Gowdra | 6450343 | 2020-01-07 10:59:10 +0530 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2019-present Open Networking Foundation |
| 3 | |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | //Package resourcemanager provides the utility for managing resources |
| 18 | package core |
| 19 | |
| 20 | import ( |
Girish Gowdra | 6450343 | 2020-01-07 10:59:10 +0530 | [diff] [blame] | 21 | "strconv" |
| 22 | "strings" |
Girish Gowdra | aeceb84 | 2020-08-21 12:10:39 -0700 | [diff] [blame^] | 23 | "sync" |
Girish Gowdra | 6450343 | 2020-01-07 10:59:10 +0530 | [diff] [blame] | 24 | |
Orhan Kupusoglu | 66b00d8 | 2020-03-13 12:06:33 +0300 | [diff] [blame] | 25 | "github.com/opencord/voltha-lib-go/v3/pkg/log" |
| 26 | ponrmgr "github.com/opencord/voltha-lib-go/v3/pkg/ponresourcemanager" |
| 27 | "github.com/opencord/voltha-protos/v3/go/openolt" |
| 28 | "golang.org/x/net/context" |
Girish Gowdra | 6450343 | 2020-01-07 10:59:10 +0530 | [diff] [blame] | 29 | ) |
| 30 | |
| 31 | func init() { |
| 32 | _, _ = log.AddPackage(log.JSON, log.DebugLevel, nil) |
| 33 | } |
| 34 | |
| 35 | // OpenOltResourceMgr holds resource related information as provided below for each field |
| 36 | type OpenOltResourceMgr struct { |
| 37 | deviceInfo *openolt.DeviceInfo |
Girish Gowdra | aeceb84 | 2020-08-21 12:10:39 -0700 | [diff] [blame^] | 38 | |
| 39 | // This protects concurrent onu_id allocate/delete calls on a per PON port basis |
| 40 | OnuIDMgmtLock []sync.RWMutex |
| 41 | // This protects concurrent flow_id allocate/delete calls. We do not need this on a |
| 42 | // per PON port basis as flow IDs are unique across the OLT. |
| 43 | FlowIDMgmtLock sync.RWMutex |
| 44 | |
| 45 | // This protects concurrent GemID and AllocID allocate/delete calls on a per PON port basis |
| 46 | GemIDAllocIDLock []sync.RWMutex |
| 47 | |
Girish Gowdra | 6450343 | 2020-01-07 10:59:10 +0530 | [diff] [blame] | 48 | // array of pon resource managers per interface technology |
| 49 | ResourceMgrs map[uint32]*ponrmgr.PONResourceManager |
| 50 | } |
| 51 | |
| 52 | // NewResourceMgr init a New resource manager instance which in turn instantiates pon resource manager |
| 53 | // instances according to technology. Initializes the default resource ranges for all |
| 54 | // the resources. |
| 55 | func NewResourceMgr(deviceID string, KVStoreHostPort string, kvStoreType string, deviceType string, devInfo *openolt.DeviceInfo) *OpenOltResourceMgr { |
| 56 | var ResourceMgr OpenOltResourceMgr |
| 57 | log.Debugf("Init new resource manager") |
| 58 | |
| 59 | ResourceMgr.deviceInfo = devInfo |
Girish Gowdra | aeceb84 | 2020-08-21 12:10:39 -0700 | [diff] [blame^] | 60 | NumPONPorts := devInfo.GetPonPorts() |
| 61 | |
| 62 | ResourceMgr.OnuIDMgmtLock = make([]sync.RWMutex, NumPONPorts) |
| 63 | ResourceMgr.GemIDAllocIDLock = make([]sync.RWMutex, NumPONPorts) |
| 64 | ResourceMgr.FlowIDMgmtLock = sync.RWMutex{} |
Girish Gowdra | 6450343 | 2020-01-07 10:59:10 +0530 | [diff] [blame] | 65 | |
| 66 | Ranges := make(map[string]*openolt.DeviceInfo_DeviceResourceRanges) |
| 67 | RsrcMgrsByTech := make(map[string]*ponrmgr.PONResourceManager) |
| 68 | ResourceMgr.ResourceMgrs = make(map[uint32]*ponrmgr.PONResourceManager) |
| 69 | |
| 70 | // TODO self.args = registry('main').get_args() |
| 71 | |
| 72 | /* |
| 73 | If a legacy driver returns protobuf without any ranges,s synthesize one from |
| 74 | the legacy global per-device information. This, in theory, is temporary until |
| 75 | the legacy drivers are upgrade to support pool ranges. |
| 76 | */ |
| 77 | if devInfo.Ranges == nil { |
| 78 | var ranges openolt.DeviceInfo_DeviceResourceRanges |
| 79 | ranges.Technology = devInfo.GetTechnology() |
| 80 | |
Girish Gowdra | 6450343 | 2020-01-07 10:59:10 +0530 | [diff] [blame] | 81 | var index uint32 |
| 82 | for index = 0; index < NumPONPorts; index++ { |
| 83 | ranges.IntfIds = append(ranges.IntfIds, index) |
| 84 | } |
| 85 | |
| 86 | var Pool openolt.DeviceInfo_DeviceResourceRanges_Pool |
| 87 | Pool.Type = openolt.DeviceInfo_DeviceResourceRanges_Pool_ONU_ID |
| 88 | Pool.Start = devInfo.OnuIdStart |
| 89 | Pool.End = devInfo.OnuIdEnd |
| 90 | Pool.Sharing = openolt.DeviceInfo_DeviceResourceRanges_Pool_DEDICATED_PER_INTF |
| 91 | onuPool := Pool |
| 92 | ranges.Pools = append(ranges.Pools, &onuPool) |
| 93 | |
| 94 | Pool.Type = openolt.DeviceInfo_DeviceResourceRanges_Pool_ALLOC_ID |
| 95 | Pool.Start = devInfo.AllocIdStart |
| 96 | Pool.End = devInfo.AllocIdEnd |
| 97 | Pool.Sharing = openolt.DeviceInfo_DeviceResourceRanges_Pool_SHARED_BY_ALL_INTF_ALL_TECH |
| 98 | allocPool := Pool |
| 99 | ranges.Pools = append(ranges.Pools, &allocPool) |
| 100 | |
| 101 | Pool.Type = openolt.DeviceInfo_DeviceResourceRanges_Pool_GEMPORT_ID |
| 102 | Pool.Start = devInfo.GemportIdStart |
| 103 | Pool.End = devInfo.GemportIdEnd |
| 104 | Pool.Sharing = openolt.DeviceInfo_DeviceResourceRanges_Pool_SHARED_BY_ALL_INTF_ALL_TECH |
| 105 | gemPool := Pool |
| 106 | ranges.Pools = append(ranges.Pools, &gemPool) |
| 107 | |
| 108 | Pool.Type = openolt.DeviceInfo_DeviceResourceRanges_Pool_FLOW_ID |
| 109 | Pool.Start = devInfo.FlowIdStart |
| 110 | Pool.End = devInfo.FlowIdEnd |
| 111 | Pool.Sharing = openolt.DeviceInfo_DeviceResourceRanges_Pool_SHARED_BY_ALL_INTF_ALL_TECH |
| 112 | ranges.Pools = append(ranges.Pools, &Pool) |
| 113 | // Add to device info |
| 114 | devInfo.Ranges = append(devInfo.Ranges, &ranges) |
| 115 | } |
| 116 | |
| 117 | // Create a separate Resource Manager instance for each range. This assumes that |
| 118 | // each technology is represented by only a single range |
| 119 | var GlobalPONRsrcMgr *ponrmgr.PONResourceManager |
| 120 | var err error |
| 121 | IPPort := strings.Split(KVStoreHostPort, ":") |
| 122 | for _, TechRange := range devInfo.Ranges { |
| 123 | technology := TechRange.Technology |
| 124 | log.Debugf("Device info technology %s", technology) |
| 125 | Ranges[technology] = TechRange |
| 126 | port, _ := strconv.Atoi(IPPort[1]) |
| 127 | RsrcMgrsByTech[technology], err = ponrmgr.NewPONResourceManager(technology, deviceType, deviceID, |
| 128 | kvStoreType, IPPort[0], port) |
| 129 | if err != nil { |
| 130 | log.Errorf("Failed to create pon resource manager instance for technology %s", technology) |
| 131 | return nil |
| 132 | } |
| 133 | // resource_mgrs_by_tech[technology] = resource_mgr |
| 134 | if GlobalPONRsrcMgr == nil { |
| 135 | GlobalPONRsrcMgr = RsrcMgrsByTech[technology] |
| 136 | } |
| 137 | for _, IntfID := range TechRange.IntfIds { |
| 138 | ResourceMgr.ResourceMgrs[(IntfID)] = RsrcMgrsByTech[technology] |
| 139 | } |
| 140 | // self.initialize_device_resource_range_and_pool(resource_mgr, global_resource_mgr, arange) |
| 141 | InitializeDeviceResourceRangeAndPool(RsrcMgrsByTech[technology], GlobalPONRsrcMgr, |
| 142 | TechRange, devInfo) |
| 143 | } |
| 144 | // After we have initialized resource ranges, initialize the |
| 145 | // resource pools accordingly. |
| 146 | for _, PONRMgr := range RsrcMgrsByTech { |
Orhan Kupusoglu | 66b00d8 | 2020-03-13 12:06:33 +0300 | [diff] [blame] | 147 | _ = PONRMgr.InitDeviceResourcePool(context.Background()) |
Girish Gowdra | 6450343 | 2020-01-07 10:59:10 +0530 | [diff] [blame] | 148 | } |
| 149 | log.Info("Initialization of resource manager success!") |
| 150 | return &ResourceMgr |
| 151 | } |
| 152 | |
| 153 | // InitializeDeviceResourceRangeAndPool initializes the resource range pool according to the sharing type, then apply |
| 154 | // device specific information. If KV doesn't exist |
| 155 | // or is broader than the device, the device's information will |
| 156 | // dictate the range limits |
| 157 | func InitializeDeviceResourceRangeAndPool(ponRMgr *ponrmgr.PONResourceManager, globalPONRMgr *ponrmgr.PONResourceManager, |
| 158 | techRange *openolt.DeviceInfo_DeviceResourceRanges, devInfo *openolt.DeviceInfo) { |
| 159 | |
| 160 | // init the resource range pool according to the sharing type |
| 161 | |
| 162 | log.Debugf("Resource range pool init for technology %s", ponRMgr.Technology) |
| 163 | // first load from KV profiles |
Orhan Kupusoglu | 66b00d8 | 2020-03-13 12:06:33 +0300 | [diff] [blame] | 164 | status := ponRMgr.InitResourceRangesFromKVStore(context.Background()) |
Girish Gowdra | 6450343 | 2020-01-07 10:59:10 +0530 | [diff] [blame] | 165 | if !status { |
| 166 | log.Debugf("Failed to load resource ranges from KV store for tech %s", ponRMgr.Technology) |
| 167 | } |
| 168 | |
| 169 | /* |
| 170 | Then apply device specific information. If KV doesn't exist |
| 171 | or is broader than the device, the device's information will |
| 172 | dictate the range limits |
| 173 | */ |
| 174 | log.Debugf("Using device info to init pon resource ranges for tech", ponRMgr.Technology) |
| 175 | |
| 176 | ONUIDStart := devInfo.OnuIdStart |
| 177 | ONUIDEnd := devInfo.OnuIdEnd |
| 178 | ONUIDShared := openolt.DeviceInfo_DeviceResourceRanges_Pool_DEDICATED_PER_INTF |
| 179 | ONUIDSharedPoolID := uint32(0) |
| 180 | AllocIDStart := devInfo.AllocIdStart |
| 181 | AllocIDEnd := devInfo.AllocIdEnd |
| 182 | AllocIDShared := openolt.DeviceInfo_DeviceResourceRanges_Pool_SHARED_BY_ALL_INTF_ALL_TECH // TODO EdgeCore/BAL limitation |
| 183 | AllocIDSharedPoolID := uint32(0) |
| 184 | GEMPortIDStart := devInfo.GemportIdStart |
| 185 | GEMPortIDEnd := devInfo.GemportIdEnd |
| 186 | GEMPortIDShared := openolt.DeviceInfo_DeviceResourceRanges_Pool_SHARED_BY_ALL_INTF_ALL_TECH // TODO EdgeCore/BAL limitation |
| 187 | GEMPortIDSharedPoolID := uint32(0) |
| 188 | FlowIDStart := devInfo.FlowIdStart |
| 189 | FlowIDEnd := devInfo.FlowIdEnd |
| 190 | FlowIDShared := openolt.DeviceInfo_DeviceResourceRanges_Pool_SHARED_BY_ALL_INTF_ALL_TECH // TODO EdgeCore/BAL limitation |
| 191 | FlowIDSharedPoolID := uint32(0) |
| 192 | |
| 193 | var FirstIntfPoolID uint32 |
| 194 | var SharedPoolID uint32 |
| 195 | |
| 196 | /* |
| 197 | * As a zero check is made against SharedPoolID to check whether the resources are shared across all intfs |
| 198 | * if resources are shared across interfaces then SharedPoolID is given a positive number. |
| 199 | */ |
| 200 | for _, FirstIntfPoolID = range techRange.IntfIds { |
| 201 | // skip the intf id 0 |
| 202 | if FirstIntfPoolID == 0 { |
| 203 | continue |
| 204 | } |
| 205 | break |
| 206 | } |
| 207 | |
| 208 | for _, RangePool := range techRange.Pools { |
| 209 | if RangePool.Sharing == openolt.DeviceInfo_DeviceResourceRanges_Pool_SHARED_BY_ALL_INTF_ALL_TECH { |
| 210 | SharedPoolID = FirstIntfPoolID |
| 211 | } else if RangePool.Sharing == openolt.DeviceInfo_DeviceResourceRanges_Pool_SHARED_BY_ALL_INTF_SAME_TECH { |
| 212 | SharedPoolID = FirstIntfPoolID |
| 213 | } else { |
| 214 | SharedPoolID = 0 |
| 215 | } |
| 216 | if RangePool.Type == openolt.DeviceInfo_DeviceResourceRanges_Pool_ONU_ID { |
| 217 | ONUIDStart = RangePool.Start |
| 218 | ONUIDEnd = RangePool.End |
| 219 | ONUIDShared = RangePool.Sharing |
| 220 | ONUIDSharedPoolID = SharedPoolID |
| 221 | } else if RangePool.Type == openolt.DeviceInfo_DeviceResourceRanges_Pool_ALLOC_ID { |
| 222 | AllocIDStart = RangePool.Start |
| 223 | AllocIDEnd = RangePool.End |
| 224 | AllocIDShared = RangePool.Sharing |
| 225 | AllocIDSharedPoolID = SharedPoolID |
| 226 | } else if RangePool.Type == openolt.DeviceInfo_DeviceResourceRanges_Pool_GEMPORT_ID { |
| 227 | GEMPortIDStart = RangePool.Start |
| 228 | GEMPortIDEnd = RangePool.End |
| 229 | GEMPortIDShared = RangePool.Sharing |
| 230 | GEMPortIDSharedPoolID = SharedPoolID |
| 231 | } else if RangePool.Type == openolt.DeviceInfo_DeviceResourceRanges_Pool_FLOW_ID { |
| 232 | FlowIDStart = RangePool.Start |
| 233 | FlowIDEnd = RangePool.End |
| 234 | FlowIDShared = RangePool.Sharing |
| 235 | FlowIDSharedPoolID = SharedPoolID |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | log.Debugw("Device info init", log.Fields{"technology": techRange.Technology, |
| 240 | "onu_id_start": ONUIDStart, "onu_id_end": ONUIDEnd, "onu_id_shared_pool_id": ONUIDSharedPoolID, |
| 241 | "alloc_id_start": AllocIDStart, "alloc_id_end": AllocIDEnd, |
| 242 | "alloc_id_shared_pool_id": AllocIDSharedPoolID, |
| 243 | "gemport_id_start": GEMPortIDStart, "gemport_id_end": GEMPortIDEnd, |
| 244 | "gemport_id_shared_pool_id": GEMPortIDSharedPoolID, |
| 245 | "flow_id_start": FlowIDStart, |
| 246 | "flow_id_end_idx": FlowIDEnd, |
| 247 | "flow_id_shared_pool_id": FlowIDSharedPoolID, |
| 248 | "intf_ids": techRange.IntfIds, |
| 249 | "uni_id_start": 0, |
| 250 | "uni_id_end_idx": 1, /*MaxUNIIDperONU()*/ |
| 251 | }) |
| 252 | |
| 253 | ponRMgr.InitDefaultPONResourceRanges(ONUIDStart, ONUIDEnd, ONUIDSharedPoolID, |
| 254 | AllocIDStart, AllocIDEnd, AllocIDSharedPoolID, |
| 255 | GEMPortIDStart, GEMPortIDEnd, GEMPortIDSharedPoolID, |
| 256 | FlowIDStart, FlowIDEnd, FlowIDSharedPoolID, 0, 1, |
| 257 | devInfo.PonPorts, techRange.IntfIds) |
| 258 | |
| 259 | // For global sharing, make sure to refresh both local and global resource manager instances' range |
| 260 | |
| 261 | if ONUIDShared == openolt.DeviceInfo_DeviceResourceRanges_Pool_SHARED_BY_ALL_INTF_ALL_TECH { |
| 262 | globalPONRMgr.UpdateRanges(ponrmgr.ONU_ID_START_IDX, ONUIDStart, ponrmgr.ONU_ID_END_IDX, ONUIDEnd, |
| 263 | "", 0, nil) |
| 264 | ponRMgr.UpdateRanges(ponrmgr.ONU_ID_START_IDX, ONUIDStart, ponrmgr.ONU_ID_END_IDX, ONUIDEnd, |
| 265 | "", 0, globalPONRMgr) |
| 266 | } |
| 267 | if AllocIDShared == openolt.DeviceInfo_DeviceResourceRanges_Pool_SHARED_BY_ALL_INTF_ALL_TECH { |
| 268 | globalPONRMgr.UpdateRanges(ponrmgr.ALLOC_ID_START_IDX, AllocIDStart, ponrmgr.ALLOC_ID_END_IDX, AllocIDEnd, |
| 269 | "", 0, nil) |
| 270 | |
| 271 | ponRMgr.UpdateRanges(ponrmgr.ALLOC_ID_START_IDX, AllocIDStart, ponrmgr.ALLOC_ID_END_IDX, AllocIDEnd, |
| 272 | "", 0, globalPONRMgr) |
| 273 | } |
| 274 | if GEMPortIDShared == openolt.DeviceInfo_DeviceResourceRanges_Pool_SHARED_BY_ALL_INTF_ALL_TECH { |
| 275 | globalPONRMgr.UpdateRanges(ponrmgr.GEMPORT_ID_START_IDX, GEMPortIDStart, ponrmgr.GEMPORT_ID_END_IDX, GEMPortIDEnd, |
| 276 | "", 0, nil) |
| 277 | ponRMgr.UpdateRanges(ponrmgr.GEMPORT_ID_START_IDX, GEMPortIDStart, ponrmgr.GEMPORT_ID_END_IDX, GEMPortIDEnd, |
| 278 | "", 0, globalPONRMgr) |
| 279 | } |
| 280 | if FlowIDShared == openolt.DeviceInfo_DeviceResourceRanges_Pool_SHARED_BY_ALL_INTF_ALL_TECH { |
| 281 | globalPONRMgr.UpdateRanges(ponrmgr.FLOW_ID_START_IDX, FlowIDStart, ponrmgr.FLOW_ID_END_IDX, FlowIDEnd, |
| 282 | "", 0, nil) |
| 283 | ponRMgr.UpdateRanges(ponrmgr.FLOW_ID_START_IDX, FlowIDStart, ponrmgr.FLOW_ID_END_IDX, FlowIDEnd, |
| 284 | "", 0, globalPONRMgr) |
| 285 | } |
| 286 | |
| 287 | // Make sure loaded range fits the platform bit encoding ranges |
| 288 | ponRMgr.UpdateRanges(ponrmgr.UNI_ID_START_IDX, 0, ponrmgr.UNI_ID_END_IDX /* TODO =OpenOltPlatform.MAX_UNIS_PER_ONU-1*/, 1, "", 0, nil) |
| 289 | } |
| 290 | |
| 291 | // Delete clears used resources for the particular olt device being deleted |
| 292 | func (RsrcMgr *OpenOltResourceMgr) Delete() error { |
Girish Gowdra | 6450343 | 2020-01-07 10:59:10 +0530 | [diff] [blame] | 293 | for _, rsrcMgr := range RsrcMgr.ResourceMgrs { |
Orhan Kupusoglu | 66b00d8 | 2020-03-13 12:06:33 +0300 | [diff] [blame] | 294 | if err := rsrcMgr.ClearDeviceResourcePool(context.Background()); err != nil { |
Girish Gowdra | 6450343 | 2020-01-07 10:59:10 +0530 | [diff] [blame] | 295 | log.Debug("Failed to clear device resource pool") |
| 296 | return err |
| 297 | } |
| 298 | } |
| 299 | log.Debug("Cleared device resource pool") |
| 300 | return nil |
| 301 | } |
| 302 | |
| 303 | // GetONUID returns the available OnuID for the given pon-port |
| 304 | func (RsrcMgr *OpenOltResourceMgr) GetONUID(ponIntfID uint32) (uint32, error) { |
Girish Gowdra | aeceb84 | 2020-08-21 12:10:39 -0700 | [diff] [blame^] | 305 | RsrcMgr.OnuIDMgmtLock[ponIntfID].Lock() |
| 306 | defer RsrcMgr.OnuIDMgmtLock[ponIntfID].Unlock() |
Girish Gowdra | 6450343 | 2020-01-07 10:59:10 +0530 | [diff] [blame] | 307 | // Check if Pon Interface ID is present in Resource-manager-map |
Girish Gowdra | aeceb84 | 2020-08-21 12:10:39 -0700 | [diff] [blame^] | 308 | ONUIDs, err := RsrcMgr.ResourceMgrs[ponIntfID].GetResourceID(context.Background(), ponIntfID, |
Girish Gowdra | 6450343 | 2020-01-07 10:59:10 +0530 | [diff] [blame] | 309 | ponrmgr.ONU_ID, 1) |
| 310 | if err != nil { |
| 311 | log.Errorf("Failed to get resource for interface %d for type %s", |
| 312 | ponIntfID, ponrmgr.ONU_ID) |
Girish Gowdra | aeceb84 | 2020-08-21 12:10:39 -0700 | [diff] [blame^] | 313 | return uint32(0), err |
Girish Gowdra | 6450343 | 2020-01-07 10:59:10 +0530 | [diff] [blame] | 314 | } |
Girish Gowdra | aeceb84 | 2020-08-21 12:10:39 -0700 | [diff] [blame^] | 315 | return ONUIDs[0], err |
Girish Gowdra | 6450343 | 2020-01-07 10:59:10 +0530 | [diff] [blame] | 316 | } |
| 317 | |
Girish Gowdra | aeceb84 | 2020-08-21 12:10:39 -0700 | [diff] [blame^] | 318 | // GetFlowID return flow ID for a given pon interface id, onu id and uni id |
| 319 | func (RsrcMgr *OpenOltResourceMgr) GetFlowID(ctx context.Context, ponIntfID uint32) (uint32, error) { |
| 320 | RsrcMgr.FlowIDMgmtLock.Lock() |
| 321 | defer RsrcMgr.FlowIDMgmtLock.Unlock() |
| 322 | FlowIDs, err := RsrcMgr.ResourceMgrs[ponIntfID].GetResourceID(context.Background(), ponIntfID, |
| 323 | ponrmgr.FLOW_ID, 1) |
Girish Gowdra | 6450343 | 2020-01-07 10:59:10 +0530 | [diff] [blame] | 324 | if err != nil { |
Girish Gowdra | aeceb84 | 2020-08-21 12:10:39 -0700 | [diff] [blame^] | 325 | log.Errorf("Failed to get resource for interface %d for type %s", |
| 326 | ponIntfID, ponrmgr.FLOW_ID) |
| 327 | return uint32(0), err |
Girish Gowdra | 6450343 | 2020-01-07 10:59:10 +0530 | [diff] [blame] | 328 | } |
Girish Gowdra | aeceb84 | 2020-08-21 12:10:39 -0700 | [diff] [blame^] | 329 | return FlowIDs[0], err |
Girish Gowdra | 6450343 | 2020-01-07 10:59:10 +0530 | [diff] [blame] | 330 | } |