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, &params)
   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  }