diff --git a/cmd/ping.go b/cmd/ping.go new file mode 100644 index 00000000..019291d5 --- /dev/null +++ b/cmd/ping.go @@ -0,0 +1,52 @@ +package cmd + +import ( + "log" + "sort" + "sync" + + "github.com/cdalar/onctl/internal/cloud" + "github.com/cdalar/onctl/internal/tools" + "github.com/spf13/cobra" +) + +var pingCmd = &cobra.Command{ + Use: "ping", + Short: "latency tests on multi cloud providers", + Run: func(cmd *cobra.Command, args []string) { + log.Println("[DEBUG] Ping") + messages := make(chan cloud.Location, 100) + listOfLocations, err := provider.Locations() + if err != nil { + log.Fatalln(err) + } + var wg sync.WaitGroup + for _, location := range listOfLocations { + wg.Add(1) + go func(location cloud.Location) { + defer wg.Done() + location.Latency, err = tools.Ping(location.Endpoint) + if err != nil { + log.Fatalln(err) + } + log.Println("[DEBUG]", location.Name, location.Latency) + messages <- location + + }(location) + } + wg.Wait() + close(messages) + list := make([]cloud.Location, 0, 100) + for message := range messages { + list = append(list, message) + } + + sort.Slice(list, func(i, j int) bool { + return list[i].Latency < list[j].Latency + }) + + log.Println("[DEBUG] Location List: ", list) + tmpl := "LOCATION\tLATENCY\n{{range .}}{{.Name}}\t{{.Latency}}\n{{end}}" + TabWriter(list, tmpl) + }, +} diff --git a/cmd/root.go b/cmd/root.go index 9377aa4f..2611dcd8 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -65,6 +65,7 @@ func Execute() error { case "gcp": provider = &cloud.ProviderGcp{ Client: providergcp.GetClient(), + Regions: providergcp.GetRegionsClient(), GroupClient: providergcp.GetGroupClient(), } @@ -92,4 +93,5 @@ func init() { rootCmd.AddCommand(destroyCmd) rootCmd.AddCommand(sshCmd) rootCmd.AddCommand(initCmd) + rootCmd.AddCommand(pingCmd) } diff --git a/internal/cloud/aws.go b/internal/cloud/aws.go index 2477913e..cdba681b 100644 --- a/internal/cloud/aws.go +++ b/internal/cloud/aws.go @@ -136,6 +136,28 @@ func (p ProviderAws) Deploy(server Vm) (Vm, error) { return mapAwsServer(instance), nil } +func (p ProviderAws) Locations() ([]Location, error) { + log.Println("[DEBUG] Get Locations") + var locations []Location + output, err := p.Client.DescribeRegions(&ec2.DescribeRegionsInput{ + // AllRegions: aws.Bool(true), + }) + if err != nil { + log.Println(err) + } + log.Println("[DEBUG] " + output.String()) + for _, region := range output.Regions { + log.Print("[DEBUG] " + *region.RegionName) + log.Println("[DEBUG] " + *region.Endpoint) + locations = append(locations, Location{ + Name: *region.RegionName, + Endpoint: *region.Endpoint + ":443", + }) + } + + return locations, nil +} + func (p ProviderAws) Destroy(server Vm) error { if server.ID == "" { log.Println("[DEBUG] Server ID is empty") diff --git a/internal/cloud/azure.go b/internal/cloud/azure.go index a436e4ce..aeb931a3 100644 --- a/internal/cloud/azure.go +++ b/internal/cloud/azure.go @@ -303,6 +303,11 @@ func (p ProviderAzure) Destroy(server Vm) error { } +func (p ProviderAzure) Locations() ([]Location, error) { + log.Println("[DEBUG] Get Locations") + return []Location{}, nil +} + func (p ProviderAzure) SSHInto(serverName string, port int) { s, err := p.GetByName(serverName) if err != nil || s.ID == "" { diff --git a/internal/cloud/cloud.go b/internal/cloud/cloud.go index 7b440027..b2a23044 100644 --- a/internal/cloud/cloud.go +++ b/internal/cloud/cloud.go @@ -6,6 +6,19 @@ import ( "time" ) +type Location struct { + // ID + ID string + // Name is the name of the location + Name string + // Country is the country of the location + Region string + // Latency is the latency of the location + Latency time.Duration + // Endpoint is the endpoint of the location + Endpoint string +} + type VmList struct { List []Vm } @@ -78,4 +91,6 @@ type CloudProviderInterface interface { SSHInto(serverName string, port int) // GetByName gets a VM by name GetByName(serverName string) (Vm, error) + // GetLocation gets a location by name + Locations() ([]Location, error) } diff --git a/internal/cloud/gcp.go b/internal/cloud/gcp.go index 5c18485f..fe3f00d3 100644 --- a/internal/cloud/gcp.go +++ b/internal/cloud/gcp.go @@ -23,9 +23,34 @@ var ( type ProviderGcp struct { Client *compute.InstancesClient + Regions *compute.RegionsClient GroupClient *compute.InstanceGroupsClient } +func (p ProviderGcp) Locations() ([]Location, error) { + log.Println("[DEBUG] Get Locations") + regionList := make([]Location, 0, 100) + it := p.Regions.List(context.Background(), &computepb.ListRegionsRequest{ + Project: viper.GetString("gcp.project"), + }) + + for { + resp, err := it.Next() + if err == iterator.Done { + break + } + if err != nil { + log.Fatalln(err) + } + regionList = append(regionList, Location{ + Name: *resp.Name, + Endpoint: fmt.Sprintf("compute.%s.googleapis.com:443", *resp.Name), + }) + log.Println("[DEBUG] Regions:", resp) + } + return regionList, nil +} + func (p ProviderGcp) List() (VmList, error) { log.Println("[DEBUG] List Servers") cloudList := make([]Vm, 0, 100) diff --git a/internal/cloud/hetzner.go b/internal/cloud/hetzner.go index ed55831c..2bd487d3 100644 --- a/internal/cloud/hetzner.go +++ b/internal/cloud/hetzner.go @@ -97,6 +97,25 @@ func (p ProviderHetzner) Destroy(server Vm) error { return nil } +func (p ProviderHetzner) Locations() ([]Location, error) { + log.Println("[DEBUG] Get Locations") + list, err := p.Client.Location.All(context.TODO()) + if err != nil { + log.Println(err) + } + if len(list) == 0 { + return []Location{}, nil + } + locationList := make([]Location, 0, len(list)) + for _, location := range list { + locationList = append(locationList, Location{ + Name: location.Name, + Endpoint: location.Name + "-speed.hetzner.com:80", + }) + } + return locationList, nil +} + func (p ProviderHetzner) List() (VmList, error) { log.Println("[DEBUG] List Servers") list, _, err := p.Client.Server.List(context.TODO(), hcloud.ServerListOpts{ diff --git a/internal/providergcp/common.go b/internal/providergcp/common.go index 976963ac..2cdef9c1 100644 --- a/internal/providergcp/common.go +++ b/internal/providergcp/common.go @@ -16,6 +16,15 @@ func GetClient() *compute.InstancesClient { return client } +func GetRegionsClient() *compute.RegionsClient { + ctx := context.Background() + client, err := compute.NewRegionsRESTClient(ctx) + if err != nil { + log.Fatalln(err) + } + return client +} + func GetGroupClient() *compute.InstanceGroupsClient { ctx := context.Background() client, err := compute.NewInstanceGroupsRESTClient(ctx) diff --git a/internal/tools/ping.go b/internal/tools/ping.go new file mode 100644 index 00000000..beedcc40 --- /dev/null +++ b/internal/tools/ping.go @@ -0,0 +1,29 @@ +package tools + +import ( + "log" + "net" + "time" +) + +func resolveDomain(domain string) (*net.TCPAddr, error) { + return net.ResolveTCPAddr("tcp4", domain) +} + +func Ping(addr string) (time.Duration, error) { + tcpaddr, err := resolveDomain(addr) + if err != nil { + log.Fatalln("Error:", err) + } + + start := time.Now() + // fsn1-speed.hetzner.com:80 + conn, err := net.Dial("tcp", tcpaddr.String()) + if err != nil { + log.Fatalln("Error:", err) + + } + since := time.Since(start) + defer conn.Close() + return since, nil +}