[VOL-4178, VOL-3952] This commit removes flows/groups/meters persistency

This commit removes flows/groups/meters persistency from rw-core.
As part of this change, it also fixes a bug where devices were not
being loaded on an rw-core restart.  This is a necessary condition
to allow the non-persistency of flows/groups/meters to work.

This commit also renames "loader" to "cache" for the flows/groups/
meters to differentiate between data that is loaded from the KV
store and the one in cache.

Change-Id: Ib14e1450021abe30b17673c2910768fb740dba51
diff --git a/rw_core/core/device/flow/cache_test.go b/rw_core/core/device/flow/cache_test.go
new file mode 100644
index 0000000..a418904
--- /dev/null
+++ b/rw_core/core/device/flow/cache_test.go
@@ -0,0 +1,154 @@
+/*
+ * 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 flow
+
+import (
+	"bufio"
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+	"regexp"
+	"strconv"
+	"testing"
+)
+
+// TestLoadersIdentical ensures that the group, flow, and meter loaders always have an identical implementation.
+func TestLoadersIdentical(t *testing.T) {
+	types := []string{"flow", "group", "meter"}
+
+	identical := [][]string{
+		{`ofp\.OfpFlowStats`, `ofp\.OfpGroupEntry`, `ofp\.OfpMeterEntry`},
+		{`\.Id`, `\.Desc\.GroupId`, `\.Config\.MeterId`},
+		{`uint64`, `uint32`, `uint32`},
+		{`Flow`, `Group`, `Meter`},
+		{`flow`, `group`, `meter`},
+	}
+
+	regexes := make([][]*regexp.Regexp, len(identical[0]))
+	for i := range regexes {
+		regexes[i] = make([]*regexp.Regexp, len(identical))
+	}
+	for i, group := range identical {
+		for j, regexStr := range group {
+			// convert from column-wise to row-wise for convenience
+			regexes[j][i] = regexp.MustCompile(regexStr)
+		}
+	}
+
+	for i := 1; i < len(types); i++ {
+		if err := compare(regexes[0], regexes[i],
+			"../"+types[0]+"/cache.go",
+			"../"+types[i]+"/cache.go"); err != nil {
+			t.Error(err)
+			return
+		}
+	}
+}
+
+func compare(regexesA, regexesB []*regexp.Regexp, fileNameA, fileNameB string) error {
+	fileA, err := os.Open(filepath.Clean(fileNameA))
+	if err != nil {
+		return err
+	}
+	defer func() {
+		err := fileA.Close()
+		if err != nil {
+			logger.Errorf(context.Background(), "failed to close file: %v", err)
+		}
+	}()
+
+	fileB, err := os.Open(filepath.Clean(fileNameB))
+	if err != nil {
+		return err
+	}
+	defer func() {
+		err := fileB.Close()
+		if err != nil {
+			logger.Errorf(context.Background(), "failed to close file: %v", err)
+		}
+	}()
+
+	scannerA, scannerB := bufio.NewScanner(fileA), bufio.NewScanner(fileB)
+
+	// treat any number of spaces as a single space
+	spaceRegex := regexp.MustCompile(` +`)
+	// extra lines are permitted before a "blank" line, or before a lock/unlock
+	spacerRegex := regexp.MustCompile(`^(?:[^a-z]*|.*Lock\(\)|.*Unlock\(\))$`)
+	// ignore import type differences
+	libGoImportRegex := regexp.MustCompile(`^.*github\.com/opencord/voltha-protos/.*$`)
+
+	lineA, lineB := 1, 1
+linesLoop:
+	for {
+		if continueA, continueB := scannerA.Scan(), scannerB.Scan(); !continueA || !continueB {
+			// EOF
+			break linesLoop
+		}
+		textA, textB := scannerA.Text(), scannerB.Text()
+
+		// allow any number of "extra" lines just before a spacer line
+		for {
+			isSpacerA, isSpacerB := spacerRegex.MatchString(textA), spacerRegex.MatchString(textB)
+			if isSpacerA && !isSpacerB {
+				if !scannerB.Scan() {
+					// EOF
+					break linesLoop
+				}
+				lineB++
+				textB = scannerB.Text()
+				continue
+			} else if isSpacerB && !isSpacerA {
+				if !scannerA.Scan() {
+					// EOF
+					break linesLoop
+				}
+				lineA++
+				textA = scannerA.Text()
+				continue
+			}
+			break
+		}
+
+		replacedA, replacedB := textA, textB
+		for i := range regexesA {
+			replacement := "{{type" + strconv.Itoa(i) + "}}"
+			replacedA, replacedB = regexesA[i].ReplaceAllString(replacedA, replacement), regexesB[i].ReplaceAllString(replacedB, replacement)
+		}
+
+		// replace multiple spaces with single space
+		replacedA, replacedB = spaceRegex.ReplaceAllString(replacedA, " "), spaceRegex.ReplaceAllString(replacedB, " ")
+
+		// ignore voltha-protos import of ofp vs voltha
+		replacedA, replacedB = libGoImportRegex.ReplaceAllString(replacedA, "{{lib-go-import}}"), libGoImportRegex.ReplaceAllString(replacedB, "{{lib-go-import}}")
+
+		if replacedA != replacedB && textA != textB {
+			return fmt.Errorf("files which must be identical do not match: \n  %s:%d\n    %s\n  %s:%d\n    %s\n\n\t%s\n\t%s", fileNameA, lineA, textA, fileNameB, lineB, textB, replacedA, replacedB)
+		}
+
+		lineA++
+		lineB++
+	}
+
+	if err := scannerA.Err(); err != nil {
+		return err
+	}
+	if err := scannerB.Err(); err != nil {
+		return err
+	}
+	return nil
+}