github.com/jlmeeker/kismatic@v1.10.1-0.20180612190640-57f9005a1f1a/integration-tests/packet/node.go (about)

     1  package packet
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"os/exec"
     7  	"time"
     8  
     9  	"github.com/packethost/packngo"
    10  )
    11  
    12  // OS is an operating system supported on Packet
    13  type OS string
    14  
    15  const (
    16  	// Ubuntu1604LTS OS image
    17  	Ubuntu1604LTS = OS("ubuntu_16_04_image")
    18  	// CentOS7 OS image
    19  	CentOS7 = OS("centos_7_image")
    20  )
    21  
    22  // Client for managing infrastructure on Packet
    23  type Client struct {
    24  	Token     string
    25  	ProjectID string
    26  
    27  	apiClient *packngo.Client
    28  }
    29  
    30  // Node is a Packet.net node
    31  type Node struct {
    32  	ID          string
    33  	Host        string
    34  	PublicIPv4  string
    35  	PrivateIPv4 string
    36  	SSHUser     string
    37  }
    38  
    39  // CreateNode creates a node in packet with the given hostname and OS
    40  func (c Client) CreateNode(hostname string, os OS, _ map[string]string) (*Node, error) {
    41  	device := &packngo.DeviceCreateRequest{
    42  		HostName:     hostname,
    43  		OS:           string(os),
    44  		Tags:         []string{"integration-test"},
    45  		ProjectID:    c.ProjectID,
    46  		Plan:         "baremetal_0",
    47  		BillingCycle: "hourly",
    48  		Facility:     "ewr1",
    49  	}
    50  	client := c.getAPIClient()
    51  	dev, _, err := client.Devices.Create(device)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  	node := &Node{
    56  		ID: dev.ID,
    57  	}
    58  	return node, nil
    59  }
    60  
    61  func (c *Client) getAPIClient() *packngo.Client {
    62  	if c.apiClient != nil {
    63  		return c.apiClient
    64  	}
    65  	c.apiClient = packngo.NewClient("", c.Token, http.DefaultClient)
    66  	return c.apiClient
    67  }
    68  
    69  // DeleteNode deletes the node that matches the given ID
    70  func (c Client) DeleteNode(deviceID string) error {
    71  	client := c.getAPIClient()
    72  	resp, err := client.Devices.Delete(deviceID)
    73  	if err != nil {
    74  		return fmt.Errorf("failed to delete node with ID %q", deviceID)
    75  	}
    76  	if resp.StatusCode != http.StatusNoContent {
    77  		return fmt.Errorf("failed to delete node with ID %q", deviceID)
    78  	}
    79  	return nil
    80  }
    81  
    82  // GetNode returns the node that matches the given ID
    83  func (c Client) GetNode(deviceID string) (*Node, error) {
    84  	client := c.getAPIClient()
    85  	dev, _, err := client.Devices.Get(deviceID)
    86  	if err != nil {
    87  		return nil, fmt.Errorf("failed to get device %q: %v", deviceID, err)
    88  	}
    89  	if dev == nil {
    90  		return nil, fmt.Errorf("did not get a device from server")
    91  	}
    92  	node := &Node{
    93  		ID:          deviceID,
    94  		Host:        dev.Hostname,
    95  		PublicIPv4:  getPublicIPv4(dev),
    96  		PrivateIPv4: getPrivateIPv4(dev),
    97  		SSHUser:     "root",
    98  	}
    99  	return node, nil
   100  }
   101  
   102  // BlockUntilNodeAccessible blocks until the given node is accessible,
   103  // or the timeout is reached.
   104  func (c Client) BlockUntilNodeAccessible(deviceID string, timeout time.Duration, sshKey, sshUser string) error {
   105  	timeoutChan := make(chan bool, 1)
   106  	go func() {
   107  		time.Sleep(timeout)
   108  		timeoutChan <- true
   109  	}()
   110  	fmt.Printf("Waiting for node %s to be accessible", deviceID)
   111  	// Loop until we get the node IP
   112  	var nodeIP string
   113  	for {
   114  		select {
   115  		case <-timeoutChan:
   116  			return fmt.Errorf("timed out waiting for node to be accessible")
   117  		default:
   118  			dev, _, err := c.getAPIClient().Devices.Get(deviceID)
   119  			if err != nil {
   120  				continue
   121  			}
   122  			nodeIP = getPublicIPv4(dev)
   123  			fmt.Printf("\nGot node's IP: %s\n", nodeIP)
   124  		}
   125  		if nodeIP != "" {
   126  			break
   127  		}
   128  		fmt.Print(".")
   129  		time.Sleep(5 * time.Second)
   130  	}
   131  	// Loop until state is active
   132  	fmt.Print("Waiting for node state to be 'active'")
   133  	active := false
   134  	for {
   135  		select {
   136  		case <-timeoutChan:
   137  			return fmt.Errorf("timedout waiting for node to be active")
   138  		default:
   139  			dev, _, err := c.getAPIClient().Devices.Get(deviceID)
   140  			if err != nil {
   141  				continue
   142  			}
   143  			if dev.State == "active" {
   144  				active = true
   145  			}
   146  		}
   147  		if active {
   148  			break
   149  		}
   150  		fmt.Print(".")
   151  		time.Sleep(30 * time.Second)
   152  	}
   153  	// Loop until timeout or ssh is accessible
   154  	fmt.Printf("Waiting for SSH access")
   155  	for {
   156  		select {
   157  		case <-timeoutChan:
   158  			return fmt.Errorf("timed out waiting for node to be accessible")
   159  		default:
   160  			if sshAccessible(nodeIP, sshKey, sshUser) {
   161  				fmt.Printf("\nSSH is GO!\n")
   162  				return nil
   163  			}
   164  		}
   165  		fmt.Print(".")
   166  		time.Sleep(5 * time.Second)
   167  	}
   168  }
   169  
   170  func getPublicIPv4(device *packngo.Device) string {
   171  	for _, net := range device.Network {
   172  		if net.Public != true || net.AddressFamily != 4 {
   173  			continue
   174  		}
   175  		if net.Address != "" {
   176  			return net.Address
   177  		}
   178  	}
   179  	return ""
   180  }
   181  
   182  func getPrivateIPv4(device *packngo.Device) string {
   183  	for _, net := range device.Network {
   184  		if net.Public == true || net.AddressFamily != 4 {
   185  			continue
   186  		}
   187  		if net.Address != "" {
   188  			return net.Address
   189  		}
   190  	}
   191  	return ""
   192  }
   193  
   194  func sshAccessible(ip string, sshKey, sshUser string) bool {
   195  	cmd := exec.Command("ssh")
   196  	cmd.Args = append(cmd.Args, "-i", sshKey)
   197  	cmd.Args = append(cmd.Args, "-o", "ConnectTimeout=5")
   198  	cmd.Args = append(cmd.Args, "-o", "BatchMode=yes")
   199  	cmd.Args = append(cmd.Args, "-o", "StrictHostKeyChecking=no")
   200  	cmd.Args = append(cmd.Args, fmt.Sprintf("%s@%s", sshUser, ip), "exit") // just call exit if we are able to connect
   201  	err := cmd.Run()
   202  	return err == nil
   203  }