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 }