Samuel A. Falvo II | 43d8353 | 2014-07-31 14:34:48 -0700 | [diff] [blame] | 1 | // +build acceptance |
| 2 | |
| 3 | package tools |
| 4 | |
| 5 | import ( |
| 6 | "crypto/rand" |
| 7 | "fmt" |
| 8 | "github.com/rackspace/gophercloud/openstack/compute/servers" |
| 9 | "github.com/rackspace/gophercloud/openstack/identity" |
| 10 | "github.com/rackspace/gophercloud/openstack/utils" |
| 11 | "os" |
| 12 | "text/tabwriter" |
| 13 | "time" |
| 14 | ) |
| 15 | |
| 16 | var errTimeout = fmt.Errorf("Timeout.") |
| 17 | |
| 18 | type testState struct { |
| 19 | O identity.AuthOptions |
| 20 | A identity.AuthResults |
| 21 | SC *identity.ServiceCatalog |
| 22 | EPs []identity.Endpoint |
| 23 | W *tabwriter.Writer |
| 24 | ImageId string |
| 25 | FlavorId string |
| 26 | Region string |
| 27 | EP string |
| 28 | Client *servers.Client |
| 29 | CreatedServer *servers.Server |
| 30 | GottenServer *servers.Server |
| 31 | UpdatedServer *servers.Server |
| 32 | ServerName string |
| 33 | AlternateName string |
| 34 | FlavorIdResize string |
| 35 | } |
| 36 | |
| 37 | func SetupForList(service string) (*testState, error) { |
| 38 | var err error |
| 39 | |
| 40 | ts := new(testState) |
| 41 | |
| 42 | ts.O, err = utils.AuthOptions() |
| 43 | if err != nil { |
| 44 | return ts, err |
| 45 | } |
| 46 | |
| 47 | ts.A, err = identity.Authenticate(ts.O) |
| 48 | if err != nil { |
| 49 | return ts, err |
| 50 | } |
| 51 | |
| 52 | ts.SC, err = identity.GetServiceCatalog(ts.A) |
| 53 | if err != nil { |
| 54 | return ts, err |
| 55 | } |
| 56 | |
| 57 | ts.EPs, err = FindAllEndpoints(ts.SC, service) |
| 58 | if err != nil { |
| 59 | return ts, err |
| 60 | } |
| 61 | |
| 62 | ts.W = new(tabwriter.Writer) |
| 63 | ts.W.Init(os.Stdout, 2, 8, 2, ' ', 0) |
| 64 | |
| 65 | return ts, nil |
| 66 | } |
| 67 | |
| 68 | func SetupForCRUD() (*testState, error) { |
| 69 | ts, err := SetupForList("compute") |
| 70 | if err != nil { |
| 71 | return ts, err |
| 72 | } |
| 73 | |
| 74 | ts.ImageId = os.Getenv("OS_IMAGE_ID") |
| 75 | if ts.ImageId == "" { |
| 76 | return ts, fmt.Errorf("Expected OS_IMAGE_ID environment variable to be set") |
| 77 | } |
| 78 | |
| 79 | ts.FlavorId = os.Getenv("OS_FLAVOR_ID") |
| 80 | if ts.FlavorId == "" { |
| 81 | return ts, fmt.Errorf("Expected OS_FLAVOR_ID environment variable to be set") |
| 82 | } |
| 83 | |
| 84 | ts.FlavorIdResize = os.Getenv("OS_FLAVOR_ID_RESIZE") |
| 85 | if ts.FlavorIdResize == "" { |
| 86 | return ts, fmt.Errorf("Expected OS_FLAVOR_ID_RESIZE environment variable to be set") |
| 87 | } |
| 88 | |
| 89 | if ts.FlavorIdResize == ts.FlavorId { |
| 90 | return ts, fmt.Errorf("OS_FLAVOR_ID and OS_FLAVOR_ID_RESIZE cannot be the same") |
| 91 | } |
| 92 | |
| 93 | ts.Region = os.Getenv("OS_REGION_NAME") |
| 94 | if ts.Region == "" { |
| 95 | ts.Region = ts.EPs[0].Region |
| 96 | } |
| 97 | |
| 98 | ts.EP, err = FindEndpointForRegion(ts.EPs, ts.Region) |
| 99 | if err != nil { |
| 100 | return ts, err |
| 101 | } |
| 102 | |
| 103 | return ts, err |
| 104 | } |
| 105 | |
| 106 | func FindAllEndpoints(sc *identity.ServiceCatalog, service string) ([]identity.Endpoint, error) { |
| 107 | ces, err := sc.CatalogEntries() |
| 108 | if err != nil { |
| 109 | return nil, err |
| 110 | } |
| 111 | |
| 112 | for _, ce := range ces { |
| 113 | if ce.Type == service { |
| 114 | return ce.Endpoints, nil |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | return nil, fmt.Errorf(service + " endpoint not found.") |
| 119 | } |
| 120 | |
| 121 | func FindEndpointForRegion(eps []identity.Endpoint, r string) (string, error) { |
| 122 | for _, ep := range eps { |
| 123 | if ep.Region == r { |
| 124 | return ep.PublicURL, nil |
| 125 | } |
| 126 | } |
| 127 | return "", fmt.Errorf("Unknown region %s", r) |
| 128 | } |
| 129 | |
| 130 | func CountDown(ts *testState, timeout int) (bool, int, error) { |
| 131 | if timeout < 1 { |
| 132 | return false, 0, errTimeout |
| 133 | } |
| 134 | time.Sleep(1 * time.Second) |
| 135 | timeout-- |
| 136 | |
| 137 | gr, err := servers.GetDetail(ts.Client, ts.CreatedServer.Id) |
| 138 | if err != nil { |
| 139 | return false, timeout, err |
| 140 | } |
| 141 | |
| 142 | ts.GottenServer, err = servers.GetServer(gr) |
| 143 | if err != nil { |
| 144 | return false, timeout, err |
| 145 | } |
| 146 | |
| 147 | return true, timeout, nil |
| 148 | } |
| 149 | |
| 150 | func CreateServer(ts *testState) error { |
| 151 | ts.ServerName = RandomString("ACPTTEST", 16) |
| 152 | fmt.Printf("Attempting to create server: %s\n", ts.ServerName) |
| 153 | |
| 154 | ts.Client = servers.NewClient(ts.EP, ts.A, ts.O) |
| 155 | |
| 156 | cr, err := servers.Create(ts.Client, map[string]interface{}{ |
| 157 | "flavorRef": ts.FlavorId, |
| 158 | "imageRef": ts.ImageId, |
| 159 | "name": ts.ServerName, |
| 160 | }) |
| 161 | if err != nil { |
| 162 | return err |
| 163 | } |
| 164 | |
| 165 | ts.CreatedServer, err = servers.GetServer(cr) |
| 166 | return err |
| 167 | } |
| 168 | |
| 169 | func WaitForStatus(ts *testState, s string) error { |
| 170 | var ( |
| 171 | inProgress bool |
| 172 | timeout int |
| 173 | err error |
| 174 | ) |
| 175 | |
| 176 | for inProgress, timeout, err = CountDown(ts, 300); inProgress; inProgress, timeout, err = CountDown(ts, timeout) { |
| 177 | if ts.GottenServer.Id != ts.CreatedServer.Id { |
| 178 | return fmt.Errorf("created server id (%s) != gotten server id (%s)", ts.CreatedServer.Id, ts.GottenServer.Id) |
| 179 | } |
| 180 | |
| 181 | if ts.GottenServer.Status == s { |
| 182 | fmt.Printf("Server reached state %s after %d seconds (approximately)\n", s, 300-timeout) |
| 183 | break |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | if err == errTimeout { |
| 188 | fmt.Printf("Time out -- I'm not waiting around.\n") |
| 189 | err = nil |
| 190 | } |
| 191 | |
| 192 | return err |
| 193 | } |
| 194 | |
| 195 | func ChangeServerName(ts *testState) error { |
| 196 | var ( |
| 197 | inProgress bool |
| 198 | timeout int |
| 199 | ) |
| 200 | |
| 201 | ts.AlternateName = RandomString("ACPTTEST", 16) |
| 202 | for ts.AlternateName == ts.ServerName { |
| 203 | ts.AlternateName = RandomString("ACPTTEST", 16) |
| 204 | } |
| 205 | fmt.Println("Attempting to change server name") |
| 206 | |
| 207 | ur, err := servers.Update(ts.Client, ts.CreatedServer.Id, map[string]interface{}{ |
| 208 | "name": ts.AlternateName, |
| 209 | }) |
| 210 | if err != nil { |
| 211 | return err |
| 212 | } |
| 213 | |
| 214 | ts.UpdatedServer, err = servers.GetServer(ur) |
| 215 | if err != nil { |
| 216 | return err |
| 217 | } |
| 218 | |
| 219 | if ts.UpdatedServer.Id != ts.CreatedServer.Id { |
| 220 | return fmt.Errorf("Expected updated and created server to share the same ID") |
| 221 | } |
| 222 | |
| 223 | for inProgress, timeout, err = CountDown(ts, 300); inProgress; inProgress, timeout, err = CountDown(ts, timeout) { |
| 224 | if ts.GottenServer.Id != ts.UpdatedServer.Id { |
| 225 | return fmt.Errorf("Updated server ID (%s) != gotten server ID (%s)", ts.UpdatedServer.Id, ts.GottenServer.Id) |
| 226 | } |
| 227 | |
| 228 | if ts.GottenServer.Name == ts.AlternateName { |
| 229 | fmt.Printf("Server updated after %d seconds (approximately)\n", 300-timeout) |
| 230 | break |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | if err == errTimeout { |
| 235 | fmt.Printf("I'm not waiting around.\n") |
| 236 | err = nil |
| 237 | } |
| 238 | |
| 239 | return err |
| 240 | } |
| 241 | |
| 242 | func MakeNewPassword(oldPass string) string { |
| 243 | fmt.Println("Current password: "+oldPass) |
| 244 | randomPassword := RandomString("", 16) |
| 245 | for randomPassword == oldPass { |
| 246 | randomPassword = RandomString("", 16) |
| 247 | } |
| 248 | fmt.Println(" New password: "+randomPassword) |
| 249 | return randomPassword |
| 250 | } |
| 251 | |
| 252 | func ChangeAdminPassword(ts *testState) error { |
| 253 | randomPassword := MakeNewPassword(ts.CreatedServer.AdminPass) |
| 254 | |
| 255 | err := servers.ChangeAdminPassword(ts.Client, ts.CreatedServer.Id, randomPassword) |
| 256 | if err != nil { |
| 257 | return err |
| 258 | } |
| 259 | |
| 260 | err = WaitForStatus(ts, "PASSWORD") |
| 261 | if err != nil { |
| 262 | return err |
| 263 | } |
| 264 | |
| 265 | return WaitForStatus(ts, "ACTIVE") |
| 266 | } |
| 267 | |
| 268 | func RebootServer(ts *testState) error { |
| 269 | fmt.Println("Attempting reboot of server "+ts.CreatedServer.Id) |
| 270 | err := servers.Reboot(ts.Client, ts.CreatedServer.Id, servers.OSReboot) |
| 271 | if err != nil { |
| 272 | return err |
| 273 | } |
| 274 | |
| 275 | err = WaitForStatus(ts, "REBOOT") |
| 276 | if err != nil { |
| 277 | return err |
| 278 | } |
| 279 | |
| 280 | return WaitForStatus(ts, "ACTIVE") |
| 281 | } |
| 282 | |
| 283 | func RebuildServer(ts *testState) error { |
| 284 | fmt.Println("Attempting to rebuild server "+ts.CreatedServer.Id) |
| 285 | |
| 286 | newPassword := MakeNewPassword(ts.CreatedServer.AdminPass) |
| 287 | newName := RandomString("ACPTTEST", 16) |
| 288 | sr, err := servers.Rebuild(ts.Client, ts.CreatedServer.Id, newName, newPassword, ts.ImageId, nil) |
| 289 | if err != nil { |
| 290 | return err |
| 291 | } |
| 292 | |
| 293 | s, err := servers.GetServer(sr) |
| 294 | if err != nil { |
| 295 | return err |
| 296 | } |
| 297 | if s.Id != ts.CreatedServer.Id { |
| 298 | return fmt.Errorf("Expected rebuilt server ID of %s; got %s", ts.CreatedServer.Id, s.Id) |
| 299 | } |
| 300 | |
| 301 | err = WaitForStatus(ts, "REBUILD") |
| 302 | if err != nil { |
| 303 | return err |
| 304 | } |
| 305 | |
| 306 | return WaitForStatus(ts, "ACTIVE") |
| 307 | } |
| 308 | |
| 309 | func ResizeServer(ts *testState) error { |
| 310 | fmt.Println("Attempting to resize server "+ts.CreatedServer.Id) |
| 311 | |
| 312 | err := servers.Resize(ts.Client, ts.CreatedServer.Id, ts.FlavorIdResize) |
| 313 | if err != nil { |
| 314 | return err |
| 315 | } |
| 316 | |
| 317 | err = WaitForStatus(ts, "RESIZE") |
| 318 | if err != nil { |
| 319 | return err |
| 320 | } |
| 321 | |
| 322 | return WaitForStatus(ts, "VERIFY_RESIZE") |
| 323 | } |
| 324 | |
| 325 | func ConfirmResize(ts *testState) error { |
| 326 | fmt.Println("Attempting to confirm resize for server "+ts.CreatedServer.Id) |
| 327 | |
| 328 | err := servers.ConfirmResize(ts.Client, ts.CreatedServer.Id) |
| 329 | if err != nil { |
| 330 | return err |
| 331 | } |
| 332 | |
| 333 | return WaitForStatus(ts, "ACTIVE") |
| 334 | } |
| 335 | |
| 336 | func RevertResize(ts *testState) error { |
| 337 | fmt.Println("Attempting to revert resize for server "+ts.CreatedServer.Id) |
| 338 | |
| 339 | err := servers.RevertResize(ts.Client, ts.CreatedServer.Id) |
| 340 | if err != nil { |
| 341 | return err |
| 342 | } |
| 343 | |
| 344 | err = WaitForStatus(ts, "REVERT_RESIZE") |
| 345 | if err != nil { |
| 346 | return err |
| 347 | } |
| 348 | |
| 349 | return WaitForStatus(ts, "ACTIVE") |
| 350 | } |
| 351 | |
| 352 | // randomString generates a string of given length, but random content. |
| 353 | // All content will be within the ASCII graphic character set. |
| 354 | // (Implementation from Even Shaw's contribution on |
| 355 | // http://stackoverflow.com/questions/12771930/what-is-the-fastest-way-to-generate-a-long-random-string-in-go). |
| 356 | func RandomString(prefix string, n int) string { |
| 357 | const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" |
| 358 | var bytes = make([]byte, n) |
| 359 | rand.Read(bytes) |
| 360 | for i, b := range bytes { |
| 361 | bytes[i] = alphanum[b%byte(len(alphanum))] |
| 362 | } |
| 363 | return prefix + string(bytes) |
| 364 | } |