text: /*
  Copyright 2014 Rohith All rights reserved.
  
  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 marathon
  
  import (
  	"fmt"
  	"net/http"
  	"net/url"
  	"strings"
  	"sync"
  	"time"
  )
  
  const (
  	memberStatusUp   = 0
  	memberStatusDown = 1
  )
  
  // the status of a member node
  type memberStatus int
  
  // cluster is a collection of marathon nodes
  type cluster struct {
  	sync.RWMutex
  	// a collection of nodes
  	members []*member
  	// the http client
  	client *http.Client
  }
  
  // member represents an individual endpoint
  type member struct {
  	// the name / ip address of the host
  	endpoint string
  	// the status of the host
  	status memberStatus
  }
  
  // newCluster returns a new marathon cluster
  func newCluster(client *http.Client, marathonURL string) (*cluster, error) {
  	// step: extract and basic validate the endpoints
  	var members []*member
  	var defaultProto string
  
  	for _, endpoint := range strings.Split(marathonURL, ",") {
  		// step: check for nothing
  		if endpoint == "" {
  			return nil, newInvalidEndpointError("endpoint is blank")
  		}
  		// step: prepend scheme if missing on (non-initial) endpoint.
  		if !strings.HasPrefix(endpoint, "http://") && !strings.HasPrefix(endpoint, "https://") {
  			if defaultProto == "" {
  				return nil, newInvalidEndpointError("missing scheme on (first) endpoint")
  			}
  
  			endpoint = fmt.Sprintf("%s://%s", defaultProto, endpoint)
  		}
  		// step: parse the url
  		u, err := url.Parse(endpoint)
  		if err != nil {
  			return nil, newInvalidEndpointError("invalid endpoint '%s': %s", endpoint, err)
  		}
  		if defaultProto == "" {
  			defaultProto = u.Scheme
  		}
  
  		// step: check for empty hosts
  		if u.Host == "" {
  			return nil, newInvalidEndpointError("endpoint: %s must have a host", endpoint)
  		}
  
  		// step: create a new node for this endpoint
  		members = append(members, &member{endpoint: u.String()})
  	}
  
  	return &cluster{
  		client:  client,
  		members: members,
  	}, nil
  }
  
  // retrieve the current member, i.e. the current endpoint in use
  func (c *cluster) getMember() (string, error) {
  	c.RLock()
  	defer c.RUnlock()
  	for _, n := range c.members {
  		if n.status == memberStatusUp {
  			return n.endpoint, nil
  		}
  	}
  
  	return "", ErrMarathonDown
  }
  
  // markDown marks down the current endpoint
  func (c *cluster) markDown(endpoint string) {
  	c.Lock()
  	defer c.Unlock()
  	for _, n := range c.members {
  		// step: check if this is the node and it's marked as up - The double  checking on the
  		// nodes status ensures the multiple calls don't create multiple checks
  		if n.status == memberStatusUp && n.endpoint == endpoint {
  			n.status = memberStatusDown
  			go c.healthCheckNode(n)
  			break
  		}
  	}
  }
  
  // healthCheckNode performs a health check on the node and when active updates the status
  func (c *cluster) healthCheckNode(node *member) {
  	// step: wait for the node to become active ... we are assuming a /ping is enough here
  	for {
  		res, err := c.client.Get(fmt.Sprintf("%s/ping", node.endpoint))
  		if err == nil && res.StatusCode == 200 {
  			break
  		}
  		<-time.After(time.Duration(5 * time.Second))
  	}
  	// step: mark the node as active again
  	c.Lock()
  	defer c.Unlock()
  	node.status = memberStatusUp
  }
  
  // activeMembers returns a list of active members
  func (c *cluster) activeMembers() []string {
  	return c.membersList(memberStatusUp)
  }
  
  // nonActiveMembers returns a list of non-active members in the cluster
  func (c *cluster) nonActiveMembers() []string {
  	return c.membersList(memberStatusDown)
  }
  
  // memberList returns a list of members of a specified status
  func (c *cluster) membersList(status memberStatus) []string {
  	c.RLock()
  	defer c.RUnlock()
  	var list []string
  	for _, m := range c.members {
  		if m.status == status {
  			list = append(list, m.endpoint)
  		}
  	}
  
  	return list
  }
  
  // size returns the size of the cluster
  func (c *cluster) size() int {
  	return len(c.members)
  }
  
  // String returns a string representation
  func (m member) String() string {
  	status := "UP"
  	if m.status == memberStatusDown {
  		status = "DOWN"
  	}
  
  	return fmt.Sprintf("member: %s:%s", m.endpoint, status)
  }
  