github.com/ergo-services/ergo@v1.999.224/apps/cloud/client.go (about)

     1  package cloud
     2  
     3  import (
     4  	"crypto/tls"
     5  	"fmt"
     6  	"net"
     7  	"os"
     8  	"regexp"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/ergo-services/ergo/etf"
    14  	"github.com/ergo-services/ergo/gen"
    15  	"github.com/ergo-services/ergo/lib"
    16  	"github.com/ergo-services/ergo/node"
    17  )
    18  
    19  type CloudNode struct {
    20  	Node       string
    21  	Port       uint16
    22  	SkipVerify bool
    23  }
    24  
    25  type cloudClient struct {
    26  	gen.Server
    27  }
    28  
    29  type cloudClientState struct {
    30  	options   node.Cloud
    31  	handshake node.HandshakeInterface
    32  	monitor   etf.Ref
    33  	node      string
    34  }
    35  
    36  type messageCloudClientConnect struct{}
    37  
    38  func (cc *cloudClient) Init(process *gen.ServerProcess, args ...etf.Term) error {
    39  	lib.Log("[%s] CLOUD_CLIENT: Init: %#v", process.NodeName(), args)
    40  	if len(args) == 0 {
    41  		return fmt.Errorf("no args to start cloud client")
    42  	}
    43  
    44  	cloudOptions, ok := args[0].(node.Cloud)
    45  	if ok == false {
    46  		return fmt.Errorf("wrong args for the cloud client")
    47  	}
    48  
    49  	handshake, err := createHandshake(cloudOptions)
    50  	if err != nil {
    51  		return fmt.Errorf("can not create HandshakeInterface for the cloud client: %s", err)
    52  	}
    53  
    54  	process.State = &cloudClientState{
    55  		options:   cloudOptions,
    56  		handshake: handshake,
    57  	}
    58  
    59  	if err := process.RegisterEvent(EventCloud, MessageEventCloud{}); err != nil {
    60  		lib.Warning("can't register event %q: %s", EventCloud, err)
    61  	}
    62  
    63  	process.Cast(process.Self(), messageCloudClientConnect{})
    64  
    65  	return nil
    66  }
    67  
    68  func (cc *cloudClient) HandleCast(process *gen.ServerProcess, message etf.Term) gen.ServerStatus {
    69  	lib.Log("[%s] CLOUD_CLIENT: HandleCast: %#v", process.NodeName(), message)
    70  	switch message.(type) {
    71  	case messageCloudClientConnect:
    72  		state := process.State.(*cloudClientState)
    73  
    74  		// initiate connection with the cloud
    75  		cloudNodes, err := getCloudNodes()
    76  		if err != nil {
    77  			lib.Warning("can't resolve cloud nodes: %s", err)
    78  		}
    79  
    80  		// add static route with custom handshake
    81  		thisNode := process.Env(node.EnvKeyNode).(node.Node)
    82  
    83  		for _, cloud := range cloudNodes {
    84  			routeOptions := node.RouteOptions{
    85  				Cookie:    state.options.Cookie,
    86  				IsErgo:    true,
    87  				Handshake: state.handshake,
    88  			}
    89  			routeOptions.TLS = &tls.Config{
    90  				InsecureSkipVerify: cloud.SkipVerify,
    91  			}
    92  			if err := thisNode.AddStaticRoutePort(cloud.Node, cloud.Port, routeOptions); err != nil {
    93  				if err != lib.ErrTaken {
    94  					continue
    95  				}
    96  			}
    97  
    98  			lib.Log("[%s] CLOUD_CLIENT: trying to connect with: %s", process.NodeName(), cloud.Node)
    99  			if err := thisNode.Connect(cloud.Node); err != nil {
   100  				lib.Log("[%s] CLOUD_CLIENT: failed with reason: ", err)
   101  				continue
   102  			}
   103  
   104  			// add proxy domain route
   105  			proxyRoute := node.ProxyRoute{
   106  				Name:   "@" + state.options.Cluster,
   107  				Proxy:  cloud.Node,
   108  				Cookie: state.options.Cookie,
   109  			}
   110  			thisNode.AddProxyRoute(proxyRoute)
   111  
   112  			state.monitor = process.MonitorNode(cloud.Node)
   113  			state.node = cloud.Node
   114  			event := MessageEventCloud{
   115  				Cluster: proxyRoute.Name,
   116  				Online:  true,
   117  				Proxy:   cloud.Node,
   118  			}
   119  			if err := process.SendEventMessage(EventCloud, event); err != nil {
   120  				lib.Log("[%s] CLOUD_CLIENT: failed to send event (%s) %#v: %s",
   121  					process.NodeName(), EventCloud, event, err)
   122  			}
   123  			return gen.ServerStatusOK
   124  		}
   125  
   126  		// cloud nodes aren't available. make another attempt in 3 seconds
   127  		process.CastAfter(process.Self(), messageCloudClientConnect{}, 5*time.Second)
   128  	}
   129  	return gen.ServerStatusOK
   130  }
   131  
   132  func (cc *cloudClient) HandleInfo(process *gen.ServerProcess, message etf.Term) gen.ServerStatus {
   133  	lib.Log("[%s] CLOUD_CLIENT: HandleInfo: %#v", process.NodeName(), message)
   134  	state := process.State.(*cloudClientState)
   135  
   136  	switch m := message.(type) {
   137  	case gen.MessageNodeDown:
   138  		if m.Ref != state.monitor {
   139  			return gen.ServerStatusOK
   140  		}
   141  		thisNode := process.Env(node.EnvKeyNode).(node.Node)
   142  		state.cleanup(thisNode)
   143  
   144  		event := MessageEventCloud{
   145  			Online: false,
   146  		}
   147  		process.SendEventMessage(EventCloud, event)
   148  		// lost connection with the cloud node. try to connect again
   149  		process.Cast(process.Self(), messageCloudClientConnect{})
   150  	}
   151  	return gen.ServerStatusOK
   152  }
   153  
   154  func (cc *cloudClient) Terminate(process *gen.ServerProcess, reason string) {
   155  	state := process.State.(*cloudClientState)
   156  	thisNode := process.Env(node.EnvKeyNode).(node.Node)
   157  	thisNode.RemoveProxyRoute("@" + state.options.Cluster)
   158  	thisNode.Disconnect(state.node)
   159  	state.cleanup(thisNode)
   160  }
   161  
   162  func (ccs *cloudClientState) cleanup(node node.Node) {
   163  	node.RemoveStaticRoute(ccs.node)
   164  	node.RemoveProxyRoute("@" + ccs.options.Cluster)
   165  	ccs.node = ""
   166  }
   167  
   168  func getCloudNodes() ([]CloudNode, error) {
   169  	// check if custom cloud entries have been defined via env
   170  	if entries := strings.Fields(os.Getenv("ERGO_SERVICES_CLOUD")); len(entries) > 0 {
   171  		nodes := []CloudNode{}
   172  		for _, entry := range entries {
   173  			re := regexp.MustCompile("[@:]+")
   174  			nameHostPort := re.Split(entry, -1)
   175  			name := "dist"
   176  			host := "localhost"
   177  			port := 4411
   178  			switch len(nameHostPort) {
   179  			case 2:
   180  				// either abc@def or abc:def
   181  				if p, err := strconv.Atoi(nameHostPort[1]); err == nil {
   182  					port = p
   183  				} else {
   184  					name = nameHostPort[0]
   185  					host = nameHostPort[1]
   186  				}
   187  			case 3:
   188  				if p, err := strconv.Atoi(nameHostPort[2]); err == nil {
   189  					port = p
   190  				} else {
   191  					continue
   192  				}
   193  				name = nameHostPort[0]
   194  				host = nameHostPort[1]
   195  
   196  			default:
   197  				continue
   198  			}
   199  
   200  			node := CloudNode{
   201  				Node:       name + "@" + host,
   202  				Port:       uint16(port),
   203  				SkipVerify: true,
   204  			}
   205  			nodes = append(nodes, node)
   206  
   207  		}
   208  
   209  		if len(nodes) > 0 {
   210  			return nodes, nil
   211  		}
   212  	}
   213  	_, srv, err := net.LookupSRV("cloud", "dist", "ergo.services")
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  
   218  	nodes := make([]CloudNode, len(srv))
   219  	for i := range srv {
   220  		nodes[i].Node = "dist@" + strings.TrimSuffix(srv[i].Target, ".")
   221  		nodes[i].Port = srv[i].Port
   222  	}
   223  
   224  	// return only 3 of them
   225  	if len(nodes) > 3 {
   226  		return nodes[:3], nil
   227  	}
   228  	return nodes, nil
   229  }