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