VOL-381 add unum container to support ONOS cluster formation under swarm

Change-Id: Ic260edda19bb199ed040f05164ab605f28c919d0
diff --git a/unum/unum.go b/unum/unum.go
new file mode 100644
index 0000000..7b33afb
--- /dev/null
+++ b/unum/unum.go
@@ -0,0 +1,149 @@
+// Copyright 2017 the original author or authors.
+//
+// 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 main
+
+import (
+	"fmt"
+	"net/url"
+	"os"
+	"text/tabwriter"
+	"time"
+
+	"github.com/Sirupsen/logrus"
+	_ "github.com/dimiro1/banner/autoload"
+	"github.com/kelseyhightower/envconfig"
+)
+
+const (
+	// configTemplate used to display the configuration values for informational/debbug purposes
+	configTemplate = `This service is configured by the environment. The following
+are the configuration values:
+    KEY	VALUE	DESCRIPTION
+    {{range .}}{{usage_key .}}	{{.Field}}	{{usage_description .}}
+    {{end}}`
+)
+
+// configSpec configuration options for the cluster manager, see envconfig project
+type configSpec struct {
+	Orchestrator string            `envconfig:"ORCHESTRATOR" desc:"specifies how to connect to the container orchestration system" default:"swarm://"`
+	Labels       map[string]string `envconfig:"LABELS" desc:"labels to match service" default:"org.onosproject.cluster:true"`
+	Network      map[string]string `envconfig:"NETWORK" desc:"labels to match against connected networks to determine IP selection for ONOS instance" default:"org.onosproject.cluster:true"`
+	Listen       string            `envconfig:"LISTEN" desc:"interface on which to listen for cluster configuration requests" default:"0.0.0.0:5411"`
+	Period       time.Duration     `envconfig:"PERIOD" desc:"how often should the container orchestrator be queried" default:"1s"`
+	LogLevel     string            `envconfig:"LOG_LEVEL" desc:"detail level for logging" default:"warning"`
+	LogFormat    string            `envconfig:"LOG_FORMAT" desc:"log output format, text or json" default:"text"`
+}
+
+var log = logrus.New()
+
+// myStruct testing
+type myStruct struct{}
+
+// labelMatch returns true if the labels and values specified in needs are in the label map of has
+func labelMatch(has map[string]string, needs map[string]string) bool {
+	for label, val1 := range needs {
+		if val2, ok := has[label]; !ok || val2 != val1 {
+			return false
+		}
+	}
+	return true
+}
+
+func main() {
+
+	// Load configuration values and output for information/debug purposes
+	var config configSpec
+	envconfig.Process("", &config)
+	tabs := tabwriter.NewWriter(os.Stdout, 4, 4, 4, ' ', 0)
+	err := envconfig.Usagef("", &config, tabs, configTemplate)
+	if err != nil {
+		panic(err)
+	}
+	tabs.Flush()
+	fmt.Println()
+
+	// Establish logging configuraton
+	switch config.LogFormat {
+	case "json":
+		log.Formatter = &logrus.JSONFormatter{}
+	default:
+		log.Formatter = &logrus.TextFormatter{
+			FullTimestamp: true,
+			ForceColors:   true,
+		}
+	}
+	level, err := logrus.ParseLevel(config.LogLevel)
+	if err != nil {
+		log.Errorf("Invalid error level specified: '%s', defaulting to WARN level", config.LogLevel)
+		level = logrus.WarnLevel
+	}
+	log.Level = level
+
+	log.Info("Starting ONOS Cluster Manager (unum)")
+
+	// Get adapter to orchestrator
+	url, err := url.Parse(config.Orchestrator)
+	if err != nil {
+		log.Errorf("Unable to parse specified orchestrator URL, '%s' : %s", config.Orchestrator, err.Error())
+		panic(err)
+	}
+
+	// Create client for container orchestratory based on URL
+	orch, err := NewOrchestrationClient(url)
+	if err != nil {
+		log.Errorf("Unable to establish connection to container orchestrator, '%s' : %s",
+			url, err.Error())
+		panic(err)
+	}
+
+	// Get cluster information and build initial cluster configuration
+	info, err := orch.GetInfo(config.Labels, config.Network)
+	if err != nil {
+		log.Warnf("Unable to query cluster information from container orchestratory : %s",
+			err.Error())
+	}
+
+	listener := &Listener{
+		ListenOn: config.Listen,
+	}
+	listener.Init()
+
+	// Set the initial cluster configuration
+	if err == nil {
+		GenerateConfig(info, listener.Update)
+	}
+
+	// Set up the REST listener for the ONOS cluster configuraiton server
+	go listener.ListenAndServe()
+
+	// Loop forever, quering for cluster information and updating the cluster configuration
+	// when needed
+	for {
+		info, err := orch.GetInfo(config.Labels, config.Network)
+		if err != nil {
+			log.Warnf("Unable to query cluster information from container orchestrator : %s",
+				err.Error())
+		} else {
+
+			log.Debugf("EXPECTED: %d, HAVE: %d", info.Expected, len(info.Nodes))
+			if info.Expected == uint64(len(info.Nodes)) || info.Expected == 0 {
+				GenerateConfig(info, listener.Update)
+			}
+		}
+
+		// configurable pause
+		time.Sleep(config.Period)
+	}
+}