@@ -740,7 +740,12 @@ func (app *CommonApp) cmnAppCRUCommonDbOpn(d *db.DB, opcode int, dbMap map[strin
740740 A leaf-list field in redis has "@" suffix as per swsssdk convention.
741741 */
742742 resTblRw := db.Value {Field : map [string ]string {}}
743- resTblRw = checkAndProcessLeafList (existingEntry , tblRw , UPDATE , d , tblNm , tblKey )
743+ /* for north-bound REPLACE request always swap the whole leaf-list */
744+ if app .cmnAppOpcode == REPLACE {
745+ resTblRw = tblRw
746+ } else {
747+ resTblRw = checkAndProcessLeafList (existingEntry , tblRw , UPDATE , d , tblNm , tblKey )
748+ }
744749 log .Info ("Processing Table row " , resTblRw )
745750 err = d .ModEntry (cmnAppTs , db.Key {Comp : []string {tblKey }}, resTblRw )
746751 if err != nil {
@@ -773,7 +778,12 @@ func (app *CommonApp) cmnAppCRUCommonDbOpn(d *db.DB, opcode int, dbMap map[strin
773778 A leaf-list field in redis has "@" suffix as per swsssdk convention.
774779 */
775780 resTblRw := db.Value {Field : map [string ]string {}}
776- resTblRw = checkAndProcessLeafList (existingEntry , tblRw , UPDATE , d , tblNm , tblKey )
781+ /* for north-bound REPLACE request always swap the whole leaf-list */
782+ if app .cmnAppOpcode == REPLACE {
783+ resTblRw = tblRw
784+ } else {
785+ resTblRw = checkAndProcessLeafList (existingEntry , tblRw , UPDATE , d , tblNm , tblKey )
786+ }
777787 err = d .ModEntry (cmnAppTs , db.Key {Comp : []string {tblKey }}, resTblRw )
778788 if err != nil {
779789 log .Warning ("UPDATE case - d.ModEntry() failure" )
@@ -929,18 +939,195 @@ func deleteFields(existingEntry db.Value, d *db.DB, cmnAppTs *db.TableSpec, tblK
929939 return err
930940}
931941
942+ func (app * CommonApp ) processTableDeleteForReplace (d * db.DB , parentTblNm string , parentTblSpec * db.TableSpec , moduleNm string , cvlSess * cvl.CVL , ordParentTblKeys []string ) error {
943+ var err error
944+ var childTblToCleanMap map [string ]bool
945+ var parentTblKeyMap map [string ]db.Value
946+ var parentTblKeys []db.Key
947+ var getKeysErr error
948+
949+ replaceDbMap , replaceOk := app.cmnAppTableMap [REPLACE ][db.ConfigDB ]
950+
951+ /* non-table owners are filled in update by infra, subtrees can fill anywhere
952+ map-consolidation at the end of infra translation before hitting common_app
953+ should merge the data if table-instance present across operations.
954+ */
955+ updateDbMap , updateOk := app.cmnAppTableMap [UPDATE ][db.ConfigDB ]
956+ createDbMap , createOk := app.cmnAppTableMap [CREATE ][db.ConfigDB ]
957+ if ! replaceOk && ! updateOk && ! createOk {
958+ log .V (4 ).Info ("No data in REPLACE, UPDATE and CREATE result map." )
959+ return nil
960+ }
961+ if len (ordParentTblKeys ) == 0 {
962+ parentTblKeys , getKeysErr = d .GetKeys (parentTblSpec )
963+ if getKeysErr != nil {
964+ log .Warningf ("GetKeys() failed for parent table - %v - error - %v" , parentTblNm , getKeysErr )
965+ }
966+ if len (parentTblKeys ) == 0 {
967+ log .V (4 ).Info ("No DB data found so no action to take for parent table " , parentTblNm )
968+ return nil
969+ }
970+ }
971+
972+ log .Infof ("processDeleteForReplace: parent table %v" , parentTblNm )
973+
974+ childTblToCleanMap = make (map [string ]bool )
975+ for oper , dbMap := range app .cmnAppTableMap {
976+ //table/instances passed as input param to this function belong to DELETE resultmap
977+ if (oper == DELETE ) || (len (dbMap ) == 0 ) || (len (dbMap [db .ConfigDB ]) == 0 ) {
978+ continue
979+ }
980+ markChildTablesToCleanFromTranslatedResult (d , dbMap [db .ConfigDB ], childTblToCleanMap , parentTblNm , moduleNm )
981+ if log .V (4 ) {
982+ log .Infof ("populated child tables that might need cleanup after processing result tables for oper %v is %v" , oper , childTblToCleanMap )
983+ }
984+ }
985+
986+ if len (childTblToCleanMap ) == 0 {
987+ log .Info ("No child tables, mapped to/under target URI yang hierarchy remaining for cleanup." )
988+ return nil
989+ }
990+
991+ if len (ordParentTblKeys ) == 0 {
992+ parentTblKeyMap = make (map [string ]db.Value )
993+ log .V (4 ).Info ("parent table keys - " , parentTblKeys )
994+ for _ , parentKey := range parentTblKeys {
995+ parentTblKeyMap [strings .Join (parentKey .Comp , "|" )] = db.Value {}
996+ }
997+ log .V (4 ).Info ("parent table key map - " , parentTblKeyMap )
998+ ordParentTblKeys = transformer .SortSncTableDbKeys (parentTblNm , parentTblKeyMap )
999+ }
1000+ log .V (4 ).Info ("Sorted parent table keys - " , ordParentTblKeys )
1001+
1002+ for _ , parentKey := range ordParentTblKeys {
1003+ log .V (4 ).Info ("processing parentKey: " , parentKey )
1004+ parentInst := parentTblNm + "|" + parentKey
1005+ depDataList := cvlSess .GetDepDataForDelete (parentInst )
1006+ log .V (4 ).Info ("depList: " , depDataList )
1007+ for idx := len (depDataList ) - 1 ; idx >= 0 ; idx -- { //CVL gives in parent first order
1008+ depEntry := depDataList [idx ]
1009+ log .V (4 ).Infof ("Processing depEntry for item %v in depDataList " , depEntry .RefKey )
1010+ for depInst , depInstFieldMap := range depEntry .Entry {
1011+ log .V (4 ).Info ("Processing depInst:depFields " , depInst , depInstFieldMap )
1012+ childTblAndKey := strings .SplitN (depInst , "|" , 2 )
1013+ if len (childTblAndKey ) != 2 {
1014+ log .Warning ("Child table/key not found in depInst: " , depInst )
1015+ continue
1016+ }
1017+ childTblNm := childTblAndKey [0 ]
1018+ childTblKey := childTblAndKey [1 ]
1019+ if ! childTblToCleanMap [childTblNm ] {
1020+ continue
1021+ }
1022+
1023+ /* Child table mapped in the yang hierarchy from request URI will be present either
1024+ in REPLACE/UPDATE/CREATE result-map(payload/direct map to request URI) based on
1025+ table ownership, or in DELETE result-map(since a GET like traversal is done).
1026+ If present in DELETE result-map then its already processed being a child table
1027+ and no clean-up needed before cleaning parent.Clean-up needed only when present
1028+ in REPLACE/UPDATE/CREATE
1029+ */
1030+ tblOwner := true //if instance found in REPLACE map then table owner
1031+ nonTblOwnerInstRwInResultMap := db.Value {}
1032+ nonTblOwnerInstRwInResultMap .Field = make (map [string ]string )
1033+ var instInReplaceMap , instInUpdateMap , instInCreateMap bool
1034+ if _ , instInReplaceMap = replaceDbMap [childTblNm ][childTblKey ]; ! instInReplaceMap {
1035+ if nonTblOwnerInstRwInResultMap , instInUpdateMap = updateDbMap [childTblNm ][childTblKey ]; ! instInUpdateMap {
1036+ if nonTblOwnerInstRwInResultMap , instInCreateMap = createDbMap [childTblNm ][childTblKey ]; ! instInCreateMap {
1037+ log .V (4 ).Info ("Table instance not found in REPLACE/UPDATE/CREATE result map." )
1038+ continue
1039+ }
1040+ }
1041+ tblOwner = false
1042+ log .V (4 ).Infof ("Table instance %v found in UPDATE/CREATE result map with fields %v" , childTblAndKey , nonTblOwnerInstRwInResultMap )
1043+ }
1044+
1045+ //cleanup depending on table-ownership and key vs non-key based relationship between the parent and child table
1046+ childTblSpec := & db.TableSpec {Name : childTblNm }
1047+ if len (depInstFieldMap ) > 0 {
1048+ // non-key based realtionship so clean-up only dependent fields
1049+ var fieldRw db.Value
1050+ fieldRw .Field = make (map [string ]string )
1051+ for field , val := range depInstFieldMap {
1052+ fieldRw .Field [field ] = val
1053+ }
1054+ if ! tblOwner {
1055+ /* for non-table owner if dependent data has a field not mapped to/under the
1056+ target URI yang hierarchy then abort since CVL will not allow parent to be deleted
1057+ without dependency being cleaned.
1058+ */
1059+ cleanUp , cleanUpErr := checkNonTblOwnerDepChildNeedsCleanUp (fieldRw , nonTblOwnerInstRwInResultMap ,
1060+ parentInst , depInst , app .pathInfo .Path )
1061+ if cleanUpErr != nil {
1062+ return cleanUpErr
1063+ }
1064+ if ! cleanUp {
1065+ continue
1066+ }
1067+ }
1068+ err = d .DeleteEntryFields (childTblSpec , db.Key {Comp : []string {childTblKey }}, fieldRw )
1069+ if err != nil {
1070+ log .Warning ("DELETE for REPLACE case d.DeleteEntryFields() failure" )
1071+ return err
1072+ }
1073+ } else {
1074+ //key based relation between parent and child so complete child instance can be deleted
1075+ if ! tblOwner {
1076+ /* for non-table owner entire child instance cannot be deleted
1077+ if that instance in DB has a field not mapped to/under the
1078+ target URI yang hierarchy.
1079+ */
1080+ existingEntry , existingEntryErr := d .GetEntry (childTblSpec , db.Key {Comp : []string {childTblKey }})
1081+ if existingEntryErr != nil {
1082+ log .Warning ("DELETE case for REPLACE - d.GetEntry() failure" )
1083+ return err
1084+ }
1085+ if existingEntry .IsPopulated () {
1086+ cleanUp , cleanUpErr := checkNonTblOwnerDepChildNeedsCleanUp (existingEntry , nonTblOwnerInstRwInResultMap ,
1087+ parentInst , depInst , app .pathInfo .Path )
1088+ if cleanUpErr != nil {
1089+ return cleanUpErr
1090+ }
1091+ if ! cleanUp {
1092+ continue
1093+ }
1094+ }
1095+ }
1096+ err = d .DeleteEntry (childTblSpec , db.Key {Comp : []string {childTblKey }})
1097+ if err != nil {
1098+ log .Warning ("DELETE for REPLACE case - d.DeleteEntry() failure" )
1099+ return err
1100+ }
1101+ }
1102+ }
1103+ } // end of depData List loop
1104+ } //end of parent table keys loop
1105+
1106+ return nil
1107+ }
1108+
9321109func (app * CommonApp ) handleChildDeleteForOcReplaceAndDelete (d * db.DB , sortedDelTblLst []string , moduleNm string ) error {
9331110 /* resultTblLst has child first, parent later order */
1111+ var cvlSess * cvl.CVL
9341112 var err error
9351113
1114+ if app .cmnAppOpcode == REPLACE {
1115+ cvlSess , err = d .NewValidationSession ()
1116+ if err != nil || cvlSess == nil {
1117+ log .Info ("getCVLDepDataForDelete : cvl.ValidationSessOpen failed" )
1118+ return err
1119+ }
1120+ defer cvl .ValidationSessClose (cvlSess )
1121+ }
1122+
9361123 for _ , tblNm := range sortedDelTblLst {
9371124 log .V (4 ).Info ("In Yang to DB map returned from transformer looking for table = " , tblNm )
9381125 var parentKeysList []string
9391126 if tblVal , ok := app.cmnAppTableMap [DELETE ][db.ConfigDB ][tblNm ]; ok {
9401127 cmnAppTs := & db.TableSpec {Name : tblNm }
9411128 if len (tblVal ) == 0 {
9421129 log .Info ("No table instances/rows found hence mark entire table to be deleted = " , tblNm )
943- /* handle child table cleanup when North Bound oper is REPLACE- TODO.
1130+ /* handle child table cleanup when North Bound oper is REPLACE
9441131 For north bound DELETE child yang hierarchy traversal for target/request
9451132 URI will populate the relevant child tables in the result map and
9461133 SortAsPerTblDeps() on the tables in result map will take care of child table getting
@@ -949,11 +1136,16 @@ func (app *CommonApp) handleChildDeleteForOcReplaceAndDelete(d *db.DB, sortedDel
9491136 */
9501137 if app .cmnAppOpcode == REPLACE {
9511138 log .V (4 ).Info ("process Table level Delete For North Bound Replace oper" )
952- /* TODO - For North Bound REPLACE operation payload translation can result in
1139+ /* For North Bound REPLACE operation payload translation can result in
9531140 data being populated in UPDATE map based on table ownership and also the
9541141 scope of db-mapping at the target URL.DELETE map will contain whats not present
9551142 in the payload in yang hiercharchy beneath the target URL.Thus data needs to be
956- processed in order resolving dependencies to achieve the end result */
1143+ processed in order resolving dependencies to achieve the end result.
1144+ */
1145+ err = app .processTableDeleteForReplace (d , tblNm , cmnAppTs , moduleNm , cvlSess , parentKeysList )
1146+ if err != nil {
1147+ return err
1148+ }
9571149 }
9581150 log .Info ("deleting table = " , tblNm )
9591151 err = d .DeleteTable (cmnAppTs )
@@ -1002,7 +1194,7 @@ func (app *CommonApp) handleChildDeleteForOcReplaceAndDelete(d *db.DB, sortedDel
10021194 log .Info ("Table Entry from which the fields are to be deleted does not exist. Ignore error for non existant instance for idempotency" )
10031195 continue
10041196 }
1005- if log .V (3 ) {
1197+ if log .V (4 ) {
10061198 log .Info ("Fields delete" , cmnAppTs , db.Key {Comp : []string {tblKey }}, tblRw )
10071199 }
10081200 err = deleteFields (existingEntry , d , cmnAppTs , tblKey , tblRw , app .deleteEmptyEntry )
@@ -1014,7 +1206,23 @@ func (app *CommonApp) handleChildDeleteForOcReplaceAndDelete(d *db.DB, sortedDel
10141206 }
10151207 /* Delete the dependent entries for all keys in parentKeysList in case of REPLACE */
10161208 if app .cmnAppOpcode == REPLACE && len (parentKeysList ) > 0 {
1017- // TODO as mentioned in above comment
1209+ err = app .processTableDeleteForReplace (d , tblNm , cmnAppTs , moduleNm , cvlSess , parentKeysList )
1210+ if err != nil {
1211+ return err
1212+ }
1213+ }
1214+ for _ , tableKey := range parentKeysList {
1215+ log .V (4 ).Info ("Delete parent entry " , cmnAppTs , db.Key {Comp : []string {tableKey }})
1216+ err = d .DeleteEntry (cmnAppTs , db.Key {Comp : []string {tableKey }})
1217+ if err != nil {
1218+ if cvl .CVLRetCode (err .(tlerr.TranslibCVLFailure ).Code ) == cvl .CVL_SEMANTIC_KEY_NOT_EXIST {
1219+ log .V (4 ).Infof ("Ignore delete that cannot be processed for table %v key %v that does not exist. err %v" , tblNm , tableKey , err .(tlerr.TranslibCVLFailure ).CVLErrorInfo .ConstraintErrMsg )
1220+ err = nil
1221+ } else {
1222+ log .Warning ("DELETE case - d.DeleteEntry() failure" )
1223+ return err
1224+ }
1225+ }
10181226 }
10191227 }
10201228 }
@@ -1380,3 +1588,49 @@ func isPartialReplace(exstRw db.Value, replTblRw db.Value, auxRw db.Value) bool
13801588 log .Info ("returning partialReplace - " , partialReplace )
13811589 return partialReplace
13821590}
1591+
1592+ func checkNonTblOwnerDepChildNeedsCleanUp (entryTocheckAgainst db.Value , resultMapEntry db.Value , parentTblInst string , childTblInst string , targetPath string ) (bool , error ) {
1593+ foundfieldNotMapped := false
1594+ fieldNm := ""
1595+ log .Info ("checkNonTblOwnerDepChildNeedsCleanUp() - entryTocheckAgainst" , entryTocheckAgainst , "resultMapEntry" , resultMapEntry )
1596+ for field := range entryTocheckAgainst .Field {
1597+ if ! resultMapEntry .Has (field ) {
1598+ foundfieldNotMapped = true
1599+ fieldNm = field
1600+ break
1601+ }
1602+ }
1603+ if foundfieldNotMapped {
1604+ fieldNm = strings .TrimSuffix (fieldNm , "@" )
1605+ log .Warningf ("Found field %v not mapped to/under target URI yang hierarchy in child instance %v so cannot delete the child instance." , fieldNm , childTblInst )
1606+ errStr := fmt .Sprintf ("Instance %v is in use by %v for field %v(not mapped in target yang hierarchy) and cannot be deleted." , parentTblInst , childTblInst , fieldNm )
1607+ return false , tlerr.InternalError {Format : errStr , Path : targetPath }
1608+ }
1609+
1610+ return true , nil
1611+ }
1612+
1613+ func markChildTablesToCleanFromTranslatedResult (d * db.DB , resultDbMap map [string ]map [string ]db.Value , childTblToCleanMap map [string ]bool , parentTblNm string , moduleNm string ) {
1614+ for tblNm := range resultDbMap {
1615+ if tblNm == parentTblNm {
1616+ continue
1617+ }
1618+ if childTblToCleanMap [tblNm ] {
1619+ log .Info ("Table already marked for cleaning " , tblNm )
1620+ continue
1621+ }
1622+ tblSpec := & db.TableSpec {Name : tblNm }
1623+ tblKeys , getKeysErr := d .GetKeys (tblSpec )
1624+ if getKeysErr != nil {
1625+ log .Infof ("GetKeys() failed for table - %v - error - %v" , tblNm , getKeysErr )
1626+ }
1627+ if len (tblKeys ) == 0 {
1628+ log .Info ("No DB data found so no action to take for table " , tblNm )
1629+ continue
1630+ }
1631+ if ! transformer .IsDependentChildTable (tblNm , parentTblNm , moduleNm ) {
1632+ continue
1633+ }
1634+ childTblToCleanMap [tblNm ] = true
1635+ }
1636+ }
0 commit comments