github.com/nmstate/kubernetes-nmstate@v0.82.0/pkg/client/client.go (about) 1 /* 2 Copyright The Kubernetes NMState Authors. 3 4 5 Licensed under the Apache License, Version 2.0 (the "License"); 6 you may not use this file except in compliance with the License. 7 You may obtain a copy of the License at 8 9 http://www.apache.org/licenses/LICENSE-2.0 10 11 Unless required by applicable law or agreed to in writing, software 12 distributed under the License is distributed on an "AS IS" BASIS, 13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 See the License for the specific language governing permissions and 15 limitations under the License. 16 */ 17 18 package client 19 20 import ( 21 "bytes" 22 "context" 23 "fmt" 24 "os/exec" 25 "time" 26 27 "github.com/pkg/errors" 28 corev1 "k8s.io/api/core/v1" 29 apierrors "k8s.io/apimachinery/pkg/api/errors" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "sigs.k8s.io/controller-runtime/pkg/client" 32 logf "sigs.k8s.io/controller-runtime/pkg/log" 33 34 "github.com/nmstate/kubernetes-nmstate/api/names" 35 "github.com/nmstate/kubernetes-nmstate/api/shared" 36 nmstatev1beta1 "github.com/nmstate/kubernetes-nmstate/api/v1beta1" 37 "github.com/nmstate/kubernetes-nmstate/pkg/nmstatectl" 38 "github.com/nmstate/kubernetes-nmstate/pkg/probe" 39 ) 40 41 var ( 42 log = logf.Log.WithName("client") 43 ) 44 45 const ( 46 defaultGwProbeTimeout = 120 * time.Second 47 apiServerProbeTimeout = 120 * time.Second 48 // DesiredStateConfigurationTimeout doubles the default gw ping probe and API server 49 // connectivity check timeout to ensure the Checkpoint is alive before rolling it back 50 // https://nmstate.github.io/cli_guide#manual-transaction-control 51 DesiredStateConfigurationTimeout = (defaultGwProbeTimeout + apiServerProbeTimeout) * 2 52 ) 53 54 type DependencyVersions struct { 55 HandlerNmstateVersion string 56 HostNmstateVersion string 57 } 58 59 func InitializeNodeNetworkState(cli client.Client, node *corev1.Node) (*nmstatev1beta1.NodeNetworkState, error) { 60 ownerRefList := []metav1.OwnerReference{{Name: node.ObjectMeta.Name, Kind: "Node", APIVersion: "v1", UID: node.UID}} 61 62 nodeNetworkState := nmstatev1beta1.NodeNetworkState{ 63 // Create NodeNetworkState for this node 64 ObjectMeta: metav1.ObjectMeta{ 65 Name: node.ObjectMeta.Name, 66 OwnerReferences: ownerRefList, 67 Labels: names.IncludeRelationshipLabels(nil), 68 }, 69 } 70 71 err := cli.Create(context.TODO(), &nodeNetworkState) 72 if err != nil { 73 return nil, fmt.Errorf("error creating NodeNetworkState: %v, %+v", err, nodeNetworkState) 74 } 75 76 return &nodeNetworkState, nil 77 } 78 79 func CreateOrUpdateNodeNetworkState( 80 cli client.Client, 81 node *corev1.Node, 82 observedState shared.State, 83 nns *nmstatev1beta1.NodeNetworkState, 84 versions *DependencyVersions, 85 ) error { 86 if nns == nil { 87 var err error 88 nns, err = InitializeNodeNetworkState(cli, node) 89 if err != nil { 90 return err 91 } 92 } 93 return UpdateCurrentState(cli, nns, observedState, versions) 94 } 95 96 func UpdateCurrentState( 97 cli client.Client, 98 nodeNetworkState *nmstatev1beta1.NodeNetworkState, 99 observedState shared.State, 100 versions *DependencyVersions, 101 ) error { 102 if observedState.String() == nodeNetworkState.Status.CurrentState.String() { 103 log.Info("Skipping NodeNetworkState update, node network configuration not changed") 104 return nil 105 } 106 107 nodeNetworkState.Status.HandlerNmstateVersion = versions.HandlerNmstateVersion 108 nodeNetworkState.Status.HostNetworkManagerVersion = versions.HostNmstateVersion 109 110 nodeNetworkState.Status.CurrentState = observedState 111 nodeNetworkState.Status.LastSuccessfulUpdateTime = metav1.Time{Time: time.Now()} 112 113 err := cli.Status().Update(context.Background(), nodeNetworkState) 114 if err != nil { 115 if apierrors.IsNotFound(err) { 116 return errors.Wrap(err, "Request object not found, could have been deleted after reconcile request") 117 } else { 118 return errors.Wrap(err, "Error updating nodeNetworkState") 119 } 120 } 121 122 return nil 123 } 124 125 func ExecuteCommand(command string, arguments ...string) (string, error) { 126 cmd := exec.Command(command, arguments...) 127 var stdout, stderr bytes.Buffer 128 cmd.Stdout = &stdout 129 cmd.Stderr = &stderr 130 if err := cmd.Run(); err != nil { 131 return "", fmt.Errorf("failed to execute %s: '%s', '%s', '%s'", command, err.Error(), stdout.String(), stderr.String()) 132 } 133 134 return string(bytes.Trim(stdout.Bytes(), "\n")), nil 135 } 136 137 func rollback(cli client.Client, probes []probe.Probe, cause error) error { 138 message := fmt.Sprintf("rolling back desired state configuration: %s", cause) 139 err := nmstatectl.Rollback() 140 if err != nil { 141 return errors.Wrap(err, message) 142 } 143 144 // wait for system to settle after rollback 145 probesErr := probe.Run(cli, probes) 146 if probesErr != nil { 147 return errors.Wrap(errors.Wrap(probesErr, "failed running probes after rollback"), message) 148 } 149 return errors.New(message) 150 } 151 152 func ApplyDesiredState(cli client.Client, desiredState shared.State) (string, error) { 153 if string(desiredState.Raw) == "" { 154 return "Ignoring empty desired state", nil 155 } 156 157 // Before apply we get the probes that are working fine, they should be 158 // working fine after apply 159 probes := probe.Select(cli) 160 161 // Rollback before Apply to remove pending checkpoints (for example handler pod restarted 162 // before Commit) 163 nmstatectl.Rollback() 164 165 setOutput, err := nmstatectl.Set(desiredState, DesiredStateConfigurationTimeout) 166 if err != nil { 167 return setOutput, err 168 } 169 170 err = probe.Run(cli, probes) 171 if err != nil { 172 return "", rollback(cli, probes, errors.Wrap(err, "failed runnig probes after network changes")) 173 } 174 175 commitOutput, err := nmstatectl.Commit() 176 if err != nil { 177 // We cannot rollback if commit fails, just return the error 178 return commitOutput, err 179 } 180 181 commandOutput := fmt.Sprintf("setOutput: %s \n", setOutput) 182 return commandOutput, nil 183 }