github.com/jlmeeker/kismatic@v1.10.1-0.20180612190640-57f9005a1f1a/pkg/install/add_node.go (about)

     1  package install
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/apprenda/kismatic/pkg/util"
     8  )
     9  
    10  var errMissingClusterCA = errors.New("The Certificate Authority's private key and certificate used to install " +
    11  	"the cluster are required for adding worker nodes.")
    12  
    13  // AddNode adds a worker node to the original cluster described in the plan.
    14  // If successful, the updated plan is returned.
    15  func (ae *ansibleExecutor) AddNode(originalPlan *Plan, newNode Node, roles []string, restartServices bool) (*Plan, error) {
    16  	if err := checkAddNodePrereqs(ae.pki, newNode); err != nil {
    17  		return nil, err
    18  	}
    19  	updatedPlan := AddNodeToPlan(*originalPlan, newNode, roles)
    20  
    21  	// Generate node certificates
    22  	util.PrintHeader(ae.stdout, "Generating Certificate For New Node", '=')
    23  	ca, err := ae.pki.GetClusterCA()
    24  	if err != nil {
    25  		return nil, err
    26  	}
    27  	if err = ae.pki.GenerateNodeCertificate(&updatedPlan, newNode, ca); err != nil {
    28  		return nil, fmt.Errorf("error generating certificate for new node: %v", err)
    29  	}
    30  
    31  	// Run the playbook to add the node
    32  	inventory := buildInventoryFromPlan(&updatedPlan)
    33  	cc, err := ae.buildClusterCatalog(&updatedPlan)
    34  	if err != nil {
    35  		return nil, fmt.Errorf("failed to generate ansible vars: %v", err)
    36  	}
    37  
    38  	// We need to run ansible against all hosts to update the hosts files
    39  	if updatedPlan.Cluster.Networking.UpdateHostsFiles {
    40  		util.PrintHeader(ae.stdout, "Updating Hosts Files On All Nodes", '=')
    41  		t := task{
    42  			name:           "add-node-update-hosts",
    43  			playbook:       "hosts.yaml",
    44  			plan:           updatedPlan,
    45  			inventory:      inventory,
    46  			clusterCatalog: *cc,
    47  			explainer:      ae.defaultExplainer(),
    48  		}
    49  		if err = ae.execute(t); err != nil {
    50  			return nil, fmt.Errorf("error updating hosts files on all nodes: %v", err)
    51  		}
    52  	}
    53  
    54  	if restartServices {
    55  		cc.EnableRestart()
    56  	}
    57  	util.PrintHeader(ae.stdout, "Adding New Node to Cluster", '=')
    58  	t := task{
    59  		name:           "add-node",
    60  		playbook:       "kubernetes-node.yaml",
    61  		plan:           updatedPlan,
    62  		inventory:      inventory,
    63  		clusterCatalog: *cc,
    64  		explainer:      ae.defaultExplainer(),
    65  		limit:          []string{newNode.Host},
    66  	}
    67  	if err = ae.execute(t); err != nil {
    68  		return nil, fmt.Errorf("error running playbook: %v", err)
    69  	}
    70  
    71  	// Verify that the node registered with API server
    72  	util.PrintHeader(ae.stdout, "Running New Node Smoke Test", '=')
    73  	cc.NewNode = newNode.Host
    74  	t = task{
    75  		name:           "add-node-smoke-test",
    76  		playbook:       "_node-smoke-test.yaml",
    77  		plan:           updatedPlan,
    78  		inventory:      inventory,
    79  		clusterCatalog: *cc,
    80  		explainer:      ae.defaultExplainer(),
    81  		limit:          []string{newNode.Host},
    82  	}
    83  	if err = ae.execute(t); err != nil {
    84  		return nil, fmt.Errorf("error running node smoke test: %v", err)
    85  	}
    86  
    87  	// Allow access to new node to any storage volumes defined
    88  	if len(originalPlan.Storage.Nodes) > 0 {
    89  		util.PrintHeader(ae.stdout, "Updating Allowed IPs On Storage Volumes", '=')
    90  		t = task{
    91  			name:           "add-node-update-volumes",
    92  			playbook:       "_volume-update-allowed.yaml",
    93  			plan:           updatedPlan,
    94  			inventory:      inventory,
    95  			clusterCatalog: *cc,
    96  			explainer:      ae.defaultExplainer(),
    97  		}
    98  		if err = ae.execute(t); err != nil {
    99  			return nil, fmt.Errorf("error adding new node to volume allow list: %v", err)
   100  		}
   101  	}
   102  	return &updatedPlan, nil
   103  }
   104  
   105  func AddNodeToPlan(plan Plan, node Node, roles []string) Plan {
   106  	if util.Contains("worker", roles) {
   107  		plan.Worker.ExpectedCount++
   108  		plan.Worker.Nodes = append(plan.Worker.Nodes, node)
   109  	}
   110  	if util.Contains("ingress", roles) {
   111  		plan.Ingress.ExpectedCount++
   112  		plan.Ingress.Nodes = append(plan.Ingress.Nodes, node)
   113  	}
   114  	if util.Contains("storage", roles) {
   115  		plan.Storage.ExpectedCount++
   116  		plan.Storage.Nodes = append(plan.Storage.Nodes, node)
   117  	}
   118  
   119  	return plan
   120  }
   121  
   122  // ensure the assumptions we are making are solid
   123  func checkAddNodePrereqs(pki PKI, newNode Node) error {
   124  	// 1. if the node certificate is not there, we need to ensure that
   125  	// the CA is available for generating the new nodes's cert
   126  	// don't check for a valid cert here since its already being done in GenerateNodeCertificate()
   127  	certExists, err := pki.NodeCertificateExists(newNode)
   128  	if err != nil {
   129  		return fmt.Errorf("error while checking if node's certificate exists: %v", err)
   130  	}
   131  	if !certExists {
   132  		caExists, err := pki.CertificateAuthorityExists()
   133  		if err != nil {
   134  			return fmt.Errorf("error while checking if cluster CA exists: %v", err)
   135  		}
   136  		if !caExists {
   137  			return errMissingClusterCA
   138  		}
   139  	}
   140  	return nil
   141  }