Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 39 additions & 11 deletions pkg/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,21 +357,26 @@ func (dn *Daemon) EnterDegradedState(err error) {
//
// If any of the object names are the same, they will be pointer-equal.
type stateAndConfigs struct {
bootstrapping bool
state string
currentConfig *mcfgv1.MachineConfig
pendingConfig *mcfgv1.MachineConfig
desiredConfig *mcfgv1.MachineConfig
}

func (dn *Daemon) getStateAndConfigs(pendingConfigName string) (*stateAndConfigs, error) {
state, err := getNodeAnnotationExt(dn.kubeClient.CoreV1().Nodes(), dn.name, MachineConfigDaemonStateAnnotationKey, true)
_, err := os.Lstat(InitialNodeAnnotationsFilePath)
bootstrapping := false
if err != nil {
return nil, err
}
// Temporary hack: the MCS used to not write the state=done annotation
// key. If it's unset, let's write it now.
if state == "" {
state = MachineConfigDaemonStateDone
if os.IsNotExist(err) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why check if nothing different is done?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed thanks!

// The node annotation file (laid down by the MCS)
// doesn't exist, we must not be bootstrapping
} else {
return nil, err
}
} else {
bootstrapping = true
glog.Infof("In bootstrap mode")
}

currentConfigName, err := getNodeAnnotation(dn.kubeClient.CoreV1().Nodes(), dn.name, CurrentMachineConfigAnnotationKey)
Expand All @@ -386,6 +391,16 @@ func (dn *Daemon) getStateAndConfigs(pendingConfigName string) (*stateAndConfigs
if err != nil {
return nil, err
}
state, err := getNodeAnnotationExt(dn.kubeClient.CoreV1().Nodes(), dn.name, MachineConfigDaemonStateAnnotationKey, true)
if err != nil {
return nil, err
}
// Temporary hack: the MCS used to not write the state=done annotation
// key. If it's unset, let's write it now.
if state == "" {
state = MachineConfigDaemonStateDone
}

var desiredConfig *mcfgv1.MachineConfig
if currentConfigName == desiredConfigName {
desiredConfig = currentConfig
Expand Down Expand Up @@ -415,6 +430,7 @@ func (dn *Daemon) getStateAndConfigs(pendingConfigName string) (*stateAndConfigs
}

return &stateAndConfigs{
bootstrapping: bootstrapping,
currentConfig: currentConfig,
pendingConfig: pendingConfig,
desiredConfig: desiredConfig,
Expand Down Expand Up @@ -540,6 +556,22 @@ func (dn *Daemon) CheckStateOnBoot() error {
select {}
}

if state.bootstrapping {
if !dn.checkOS(state.currentConfig.Spec.OSImageURL) {
glog.Infof("Bootstrap pivot required")
// This only returns on error
return dn.updateOSAndReboot(state.currentConfig)
} else {
glog.Infof("No bootstrap pivot required; unlinking bootstrap node annotations")
// Delete the bootstrap node annotations; the
// currentConfig's osImageURL should now be *truth*.
// In other words if it drifts somehow, we go degraded.
if err := os.Remove(InitialNodeAnnotationsFilePath); err != nil {
return errors.Wrapf(err, "Removing initial node annotations file")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh neat, I like Wrapf.

}
}
}

// Validate the on-disk state against what we *expect*.
//
// In the case where we're booting a node for the first time, or the MCD
Expand Down Expand Up @@ -742,10 +774,6 @@ func (dn *Daemon) completeUpdate(desiredConfigName string) error {
// triggerUpdateWithMachineConfig starts the update. It queries the cluster for
// the current and desired config if they weren't passed.
func (dn *Daemon) triggerUpdateWithMachineConfig(currentConfig *mcfgv1.MachineConfig, desiredConfig *mcfgv1.MachineConfig) error {
if err := dn.nodeWriter.SetUpdateWorking(dn.kubeClient.CoreV1().Nodes(), dn.name); err != nil {
return err
}

if currentConfig == nil {
ccAnnotation, err := getNodeAnnotation(dn.kubeClient.CoreV1().Nodes(), dn.name, CurrentMachineConfigAnnotationKey)
if err != nil {
Expand Down
74 changes: 43 additions & 31 deletions pkg/daemon/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,41 +63,16 @@ func (dn *Daemon) writePendingState(desiredConfig *mcfgv1.MachineConfig) error {
return replaceFileContentsAtomically(pathStateJSON, b)
}

// update the node to the provided node configuration.
func (dn *Daemon) update(oldConfig, newConfig *mcfgv1.MachineConfig) error {
// updateOSAndReboot is the last step in an update(), and it can also
// be called as a special case for the "bootstrap pivot".
func (dn *Daemon) updateOSAndReboot(newConfig *mcfgv1.MachineConfig) error {
var err error

oldConfigName := oldConfig.GetName()
newConfigName := newConfig.GetName()
glog.Infof("Checking reconcilable for config %v to %v", oldConfigName, newConfigName)
// make sure we can actually reconcile this state
reconcilableError := dn.reconcilable(oldConfig, newConfig)

if reconcilableError != nil {
msg := fmt.Sprintf("Can't reconcile config %v with %v: %v", oldConfigName, newConfigName, *reconcilableError)
if dn.recorder != nil {
dn.recorder.Eventf(newConfig, corev1.EventTypeWarning, "FailedToReconcile", msg)
}
dn.logSystem(msg)
return fmt.Errorf("%s", msg)
}

// update files on disk that need updating
if err = dn.updateFiles(oldConfig, newConfig); err != nil {
return err
}

if err = dn.updateOS(newConfig); err != nil {
return err
}

if err = dn.updateSSHKeys(newConfig.Spec.Config.Passwd.Users); err != nil {
return err
}

// TODO: Change the logic to be clearer
// We need to skip draining of the node when we are running once
// and there is no cluster.
// Skip draining of the node when we're not cluster driven
if dn.onceFrom == "" {
glog.Info("Update prepared; draining the node")

Expand All @@ -120,12 +95,49 @@ func (dn *Daemon) update(oldConfig, newConfig *mcfgv1.MachineConfig) error {
glog.V(2).Infof("Node successfully drained")
}

// reboot. this function shouldn't actually return.
return dn.reboot(fmt.Sprintf("Node will reboot into config %v", newConfig.GetName()))
}

// update the node to the provided node configuration.
func (dn *Daemon) update(oldConfig, newConfig *mcfgv1.MachineConfig) error {
var err error

if dn.nodeWriter != nil {
if err = dn.nodeWriter.SetUpdateWorking(dn.kubeClient.CoreV1().Nodes(), dn.name); err != nil {
return err
}
}

oldConfigName := oldConfig.GetName()
newConfigName := newConfig.GetName()
glog.Infof("Checking reconcilable for config %v to %v", oldConfigName, newConfigName)
// make sure we can actually reconcile this state
reconcilableError := dn.reconcilable(oldConfig, newConfig)

if reconcilableError != nil {
msg := fmt.Sprintf("Can't reconcile config %v with %v: %v", oldConfigName, newConfigName, *reconcilableError)
if dn.recorder != nil {
dn.recorder.Eventf(newConfig, corev1.EventTypeWarning, "FailedToReconcile", msg)
}
dn.logSystem(msg)
return fmt.Errorf("%s", msg)
}

// update files on disk that need updating
if err = dn.updateFiles(oldConfig, newConfig); err != nil {
return err
}

if err = dn.updateSSHKeys(newConfig.Spec.Config.Passwd.Users); err != nil {
return err
}

if err = dn.writePendingState(newConfig); err != nil {
return errors.Wrapf(err, "writing pending state")
}

// reboot. this function shouldn't actually return.
return dn.reboot(fmt.Sprintf("Node will reboot into config %v", newConfigName))
return dn.updateOSAndReboot(newConfig)
}

// reconcilable checks the configs to make sure that the only changes requested
Expand Down