github.com/coreos/mantle@v0.13.0/platform/machine/aws/machine.go (about) 1 // Copyright 2016 CoreOS, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package aws 16 17 import ( 18 "fmt" 19 "os" 20 "path/filepath" 21 "strings" 22 "time" 23 24 "github.com/aws/aws-sdk-go/service/ec2" 25 "golang.org/x/crypto/ssh" 26 27 "github.com/coreos/mantle/platform" 28 "github.com/coreos/mantle/util" 29 ) 30 31 type machine struct { 32 cluster *cluster 33 mach *ec2.Instance 34 dir string 35 journal *platform.Journal 36 console string 37 } 38 39 func (am *machine) ID() string { 40 return *am.mach.InstanceId 41 } 42 43 func (am *machine) IP() string { 44 return *am.mach.PublicIpAddress 45 } 46 47 func (am *machine) PrivateIP() string { 48 return *am.mach.PrivateIpAddress 49 } 50 51 func (am *machine) RuntimeConf() platform.RuntimeConfig { 52 return am.cluster.RuntimeConf() 53 } 54 55 func (am *machine) SSHClient() (*ssh.Client, error) { 56 return am.cluster.SSHClient(am.IP()) 57 } 58 59 func (am *machine) PasswordSSHClient(user string, password string) (*ssh.Client, error) { 60 return am.cluster.PasswordSSHClient(am.IP(), user, password) 61 } 62 63 func (am *machine) SSH(cmd string) ([]byte, []byte, error) { 64 return am.cluster.SSH(am, cmd) 65 } 66 67 func (am *machine) Reboot() error { 68 return platform.RebootMachine(am, am.journal) 69 } 70 71 func (am *machine) Destroy() { 72 origConsole, err := am.cluster.flight.api.GetConsoleOutput(am.ID()) 73 if err != nil { 74 plog.Warningf("Error retrieving console log for %v: %v", am.ID(), err) 75 } 76 77 if err := am.cluster.flight.api.TerminateInstances([]string{am.ID()}); err != nil { 78 plog.Errorf("Error terminating instance %v: %v", am.ID(), err) 79 } 80 81 if am.journal != nil { 82 am.journal.Destroy() 83 } 84 85 if err := am.saveConsole(origConsole); err != nil { 86 plog.Errorf("Error saving console for instance %v: %v", am.ID(), err) 87 } 88 89 am.cluster.DelMach(am) 90 } 91 92 func (am *machine) ConsoleOutput() string { 93 return am.console 94 } 95 96 func (am *machine) saveConsole(origConsole string) error { 97 // If the instance has e.g. been running for several minutes, the 98 // returned output will be non-empty but won't necessarily include 99 // the most recent log messages. So we loop until the post-termination 100 // logs are different from the pre-termination logs. 101 err := util.WaitUntilReady(5*time.Minute, 5*time.Second, func() (bool, error) { 102 var err error 103 am.console, err = am.cluster.flight.api.GetConsoleOutput(am.ID()) 104 if err != nil { 105 return false, err 106 } 107 108 if am.console == origConsole { 109 plog.Debugf("waiting for console for %v", am.ID()) 110 return false, nil 111 } 112 113 return true, nil 114 }) 115 if err != nil { 116 err = fmt.Errorf("retrieving console output of %v: %v", am.ID(), err) 117 if origConsole != "" { 118 plog.Warning(err) 119 } else { 120 return err 121 } 122 } 123 124 // merge the two logs 125 overlapLen := 100 126 if len(am.console) < overlapLen { 127 overlapLen = len(am.console) 128 } 129 origIdx := strings.LastIndex(origConsole, am.console[0:overlapLen]) 130 if origIdx != -1 { 131 // overlap 132 am.console = origConsole[0:origIdx] + am.console 133 } else if origConsole != "" { 134 // two logs with no overlap; add scissors 135 am.console = origConsole + "\n\n8<------------------------\n\n" + am.console 136 } 137 138 path := filepath.Join(am.dir, "console.txt") 139 f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0644) 140 if err != nil { 141 return err 142 } 143 defer f.Close() 144 f.WriteString(am.console) 145 146 return nil 147 } 148 149 func (am *machine) JournalOutput() string { 150 if am.journal == nil { 151 return "" 152 } 153 154 data, err := am.journal.Read() 155 if err != nil { 156 plog.Errorf("Reading journal for instance %v: %v", am.ID(), err) 157 } 158 return string(data) 159 }