github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/network/debinterfaces/activate.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package debinterfaces 5 6 import ( 7 "fmt" 8 "os" 9 "sort" 10 "strings" 11 "time" 12 13 "github.com/juju/clock" 14 "github.com/juju/juju/utils/scriptrunner" 15 "github.com/juju/loggo" 16 "github.com/pkg/errors" 17 ) 18 19 var logger = loggo.GetLogger("juju.network.debinterfaces") 20 21 // ActivationParams contains options to use when bridging interfaces 22 type ActivationParams struct { 23 Clock clock.Clock 24 // map deviceName -> bridgeName 25 Devices map[string]string 26 DryRun bool 27 Filename string 28 ReconfigureDelay int 29 Timeout time.Duration 30 } 31 32 // ActivationResult captures the result of actively bridging the 33 // interfaces using ifup/ifdown. 34 type ActivationResult struct { 35 Stdout []byte 36 Stderr []byte 37 Code int 38 } 39 40 func activationCmd(oldContent, newContent string, params *ActivationParams) string { 41 if params.ReconfigureDelay < 0 { 42 params.ReconfigureDelay = 0 43 } 44 deviceNames := make([]string, len(params.Devices)) 45 i := 0 46 for k := range params.Devices { 47 deviceNames[i] = k 48 i++ 49 } 50 sort.Strings(deviceNames) 51 backupFilename := fmt.Sprintf("%s.backup-%d", params.Filename, params.Clock.Now().Unix()) 52 // The magic value of 25694 here causes the script to sleep for 30 seconds, simulating timeout 53 // The value of 25695 causes the script to fail. 54 return fmt.Sprintf(` 55 #!/bin/bash 56 57 set -eu 58 59 : ${DRYRUN:=} 60 61 if [ $DRYRUN ]; then 62 if [ %[4]d == 25694 ]; then sleep 30; fi 63 if [ %[4]d == 25695 ]; then echo "artificial failure" >&2; exit 1; fi 64 if [ %[4]d == 25696 ]; then echo "a very very VERY long artificial failure that should cause the code to shorten it and direct user to logs" >&2; exit 1; fi 65 fi 66 67 write_backup() { 68 cat << 'EOF' > "$1" 69 %[5]s 70 EOF 71 } 72 73 write_content() { 74 cat << 'EOF' > "$1" 75 %[6]s 76 EOF 77 } 78 79 if [ -n %[2]q ]; then 80 ${DRYRUN} write_backup %[2]q 81 fi 82 ${DRYRUN} write_content %[3]q 83 ${DRYRUN} ifdown --interfaces=%[1]q %[7]s || ${DRYRUN} ifdown --interfaces=%[1]q %[7]s || (${DRYRUN} ifup --interfaces=%[1]q -a; exit 1) 84 ${DRYRUN} sleep %[4]d 85 ${DRYRUN} cp %[3]q %[1]q 86 # we want to have full control over what happens next 87 set +e 88 ${DRYRUN} ifup --interfaces=%[1]q -a || ${DRYRUN} ifup --interfaces=%[1]q -a 89 RESULT=$? 90 if [ ${RESULT} != 0 ]; then 91 echo "Bringing up bridged interfaces failed, see system logs and %[3]q" >&2 92 ${DRYRUN} ifdown --interfaces=%[1]q %[7]s 93 ${DRYRUN} cp %[2]q %[1]q 94 ${DRYRUN} ifup --interfaces=%[1]q -a 95 exit ${RESULT} 96 fi 97 `, 98 params.Filename, 99 backupFilename, 100 params.Filename+".new", 101 params.ReconfigureDelay, 102 oldContent, 103 newContent, 104 strings.Join(deviceNames, " "))[1:] 105 } 106 107 // BridgeAndActivate will parse a debian-styled interfaces(5) file, 108 // change the stanza definitions of the requested devices to be 109 // bridged, then reconfigure the network using the ifupdown package 110 // for the new bridges. 111 func BridgeAndActivate(params ActivationParams) (*ActivationResult, error) { 112 if len(params.Devices) == 0 { 113 return nil, errors.Errorf("no devices specified") 114 } 115 116 stanzas, err := Parse(params.Filename) 117 118 if err != nil { 119 return nil, err 120 } 121 122 origContent := FormatStanzas(FlattenStanzas(stanzas), 4) 123 bridgedStanzas := Bridge(stanzas, params.Devices) 124 bridgedContent := FormatStanzas(FlattenStanzas(bridgedStanzas), 4) 125 126 if origContent == bridgedContent { 127 return nil, nil // nothing to do; old == new. 128 } 129 130 cmd := activationCmd(origContent, bridgedContent, ¶ms) 131 132 environ := os.Environ() 133 if params.DryRun { 134 environ = append(environ, "DRYRUN=echo") 135 } 136 result, err := scriptrunner.RunCommand(cmd, environ, params.Clock, params.Timeout) 137 138 activationResult := ActivationResult{ 139 Stderr: result.Stderr, 140 Stdout: result.Stdout, 141 Code: result.Code, 142 } 143 144 if err != nil { 145 return &activationResult, errors.Errorf("bridge activation error: %s", err) 146 } 147 148 logger.Infof("bridge activation result=%v", result.Code) 149 150 if result.Code != 0 { 151 logger.Errorf("bridge activation stdout\n%s\n", result.Stdout) 152 logger.Errorf("bridge activation stderr\n%s\n", result.Stderr) 153 // We want to suppress long output from ifup, ifdown - it will be shown in status message! 154 if len(result.Stderr) < 40 { 155 return &activationResult, errors.Errorf("bridge activation failed: %s", string(result.Stderr)) 156 } else { 157 return &activationResult, errors.Errorf("bridge activation failed, see logs for details") 158 } 159 } 160 161 logger.Tracef("bridge activation stdout\n%s\n", result.Stdout) 162 logger.Tracef("bridge activation stderr\n%s\n", result.Stderr) 163 164 return &activationResult, nil 165 }