github.com/IBM-Blockchain/fabric-operator@v1.0.4/pkg/manager/resources/orderernode/manager.go (about) 1 /* 2 * Copyright contributors to the Hyperledger Fabric Operator project 3 * 4 * SPDX-License-Identifier: Apache-2.0 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 package orderernode 20 21 import ( 22 "context" 23 "encoding/json" 24 "fmt" 25 "io/ioutil" 26 "path/filepath" 27 "regexp" 28 29 current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1" 30 k8sclient "github.com/IBM-Blockchain/fabric-operator/pkg/k8s/controllerclient" 31 "github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources" 32 "github.com/IBM-Blockchain/fabric-operator/pkg/operatorerrors" 33 "github.com/go-test/deep" 34 "github.com/pkg/errors" 35 k8serrors "k8s.io/apimachinery/pkg/api/errors" 36 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 37 "k8s.io/apimachinery/pkg/runtime" 38 "k8s.io/apimachinery/pkg/types" 39 "k8s.io/apimachinery/pkg/util/yaml" 40 "sigs.k8s.io/controller-runtime/pkg/client" 41 logf "sigs.k8s.io/controller-runtime/pkg/log" 42 ) 43 44 var log = logf.Log.WithName("orderernode_manager") 45 46 type Manager struct { 47 Client k8sclient.Client 48 Scheme *runtime.Scheme 49 OrdererNodeFile string 50 IgnoreDifferences []string 51 Name string 52 53 LabelsFunc func(v1.Object) map[string]string 54 OverrideFunc func(v1.Object, *current.IBPOrderer, resources.Action) error 55 } 56 57 func (m *Manager) GetName(instance v1.Object) string { 58 name := instance.GetName() 59 switch instance.(type) { 60 case *current.IBPOrderer: 61 ordererspec := instance.(*current.IBPOrderer) 62 if ordererspec.Spec.NodeNumber != nil { 63 name = fmt.Sprintf("%snode%d", instance.GetName(), *ordererspec.Spec.NodeNumber) 64 } 65 } 66 return GetName(name) 67 } 68 69 func (m *Manager) Reconcile(instance v1.Object, update bool) error { 70 name := m.GetName(instance) 71 72 orderernode := ¤t.IBPOrderer{} 73 err := m.Client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.GetNamespace()}, orderernode) 74 if err != nil { 75 if k8serrors.IsNotFound(err) { 76 log.Info(fmt.Sprintf("Creating orderernode '%s'", name)) 77 orderernode, err = m.GetOrdererNodeBasedOnCRFromFile(instance) 78 if err != nil { 79 return err 80 } 81 82 log.Info(fmt.Sprintf("Setting controller reference instance name: %s, orderernode name: %s", instance.GetName(), orderernode.GetName())) 83 err = m.Client.Create(context.TODO(), orderernode, k8sclient.CreateOption{Owner: instance, Scheme: m.Scheme}) 84 if err != nil { 85 return err 86 } 87 return nil 88 } 89 return err 90 } 91 92 if update { 93 log.Info(fmt.Sprintf("Updating orderer node is not allowed programmatically '%s'", name)) 94 return operatorerrors.New(operatorerrors.InvalidOrdererNodeUpdateRequest, "Updating orderer node is not allowed programmatically") 95 } 96 97 return nil 98 } 99 100 func (m *Manager) GetOrdererNodeBasedOnCRFromFile(instance v1.Object) (*current.IBPOrderer, error) { 101 orderernode, err := GetOrderernodeFromFile(m.OrdererNodeFile) 102 if err != nil { 103 log.Error(err, fmt.Sprintf("Error reading deployment configuration file: %s", m.OrdererNodeFile)) 104 return nil, err 105 } 106 107 return m.BasedOnCR(instance, orderernode) 108 } 109 110 func (m *Manager) BasedOnCR(instance v1.Object, orderernode *current.IBPOrderer) (*current.IBPOrderer, error) { 111 if m.OverrideFunc != nil { 112 err := m.OverrideFunc(instance, orderernode, resources.Create) 113 if err != nil { 114 return nil, operatorerrors.New(operatorerrors.InvalidOrdererNodeCreateRequest, err.Error()) 115 } 116 } 117 118 orderernode.Name = m.GetName(instance) 119 orderernode.Namespace = instance.GetNamespace() 120 orderernode.ObjectMeta.Name = m.GetName(instance) 121 orderernode.ObjectMeta.Namespace = instance.GetNamespace() 122 123 orderernode.Labels = m.LabelsFunc(instance) 124 125 return orderernode, nil 126 } 127 128 func (m *Manager) CheckState(instance v1.Object) error { 129 if instance == nil { 130 return nil // Instance has not been reconciled yet 131 } 132 133 name := m.GetName(instance) 134 135 // Get the latest version of the instance 136 orderernode := ¤t.IBPOrderer{} 137 err := m.Client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.GetNamespace()}, orderernode) 138 if err != nil { 139 return nil 140 } 141 142 copy := orderernode.DeepCopy() 143 expectedOrderernode, err := m.BasedOnCR(instance, copy) 144 if err != nil { 145 return err 146 } 147 148 deep.MaxDepth = 20 149 deep.MaxDiff = 30 150 deep.CompareUnexportedFields = true 151 deep.LogErrors = true 152 153 diff := deep.Equal(orderernode.Spec, expectedOrderernode.Spec) 154 if diff != nil { 155 err := m.ignoreDifferences(diff) 156 if err != nil { 157 return errors.Wrap(err, "orderernode has been edited manually, and does not match what is expected based on the CR") 158 } 159 } 160 161 return nil 162 } 163 164 func (m *Manager) RestoreState(instance v1.Object) error { 165 if instance == nil { 166 return nil // Instance has not been reconciled yet 167 } 168 169 name := m.GetName(instance) 170 orderernode := ¤t.IBPOrderer{} 171 err := m.Client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.GetNamespace()}, orderernode) 172 if err != nil { 173 return nil 174 } 175 176 orderernode, err = m.BasedOnCR(instance, orderernode) 177 if err != nil { 178 return err 179 } 180 181 err = m.Client.Update(context.TODO(), orderernode) 182 if err != nil { 183 return err 184 } 185 186 return nil 187 } 188 189 func (m *Manager) Get(instance v1.Object) (client.Object, error) { 190 if instance == nil { 191 return nil, nil // Instance has not been reconciled yet 192 } 193 194 name := m.GetName(instance) 195 orderernode := ¤t.IBPOrderer{} 196 err := m.Client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.GetNamespace()}, orderernode) 197 if err != nil { 198 return nil, err 199 } 200 201 return orderernode, nil 202 } 203 204 func (m *Manager) Exists(instance v1.Object) bool { 205 _, err := m.Get(instance) 206 if err != nil { 207 return false 208 } 209 210 return true 211 } 212 213 func (m *Manager) Delete(instance v1.Object) error { 214 on, err := m.Get(instance) 215 if err != nil { 216 if !k8serrors.IsNotFound(err) { 217 return err 218 } 219 } 220 221 if on == nil { 222 return nil 223 } 224 225 err = m.Client.Delete(context.TODO(), on) 226 if err != nil { 227 if !k8serrors.IsNotFound(err) { 228 return err 229 } 230 } 231 232 return nil 233 } 234 235 func (m *Manager) getSelectorLabels(instance v1.Object) map[string]string { 236 return map[string]string{ 237 "app": instance.GetName(), 238 } 239 } 240 241 func (m *Manager) ignoreDifferences(diff []string) error { 242 diffs := []string{} 243 for _, d := range diff { 244 found := false 245 for _, i := range m.differenceToIgnore() { 246 regex := regexp.MustCompile(i) 247 found = regex.MatchString(d) 248 if found { 249 break 250 } 251 } 252 if !found { 253 diffs = append(diffs, d) 254 return fmt.Errorf("unexpected mismatch: %s", d) 255 } 256 } 257 return nil 258 } 259 260 func (m *Manager) differenceToIgnore() []string { 261 d := []string{ 262 "TypeMeta", "ObjectMeta", 263 } 264 d = append(d, m.IgnoreDifferences...) 265 return d 266 } 267 268 func (m *Manager) SetCustomName(name string) { 269 // NO-OP 270 } 271 272 func GetName(instanceName string, suffix ...string) string { 273 if len(suffix) != 0 { 274 if suffix[0] != "" { 275 return fmt.Sprintf("%s-%s", instanceName, suffix[0]) 276 } 277 } 278 return fmt.Sprintf("%s", instanceName) 279 } 280 281 func GetOrderernodeFromFile(file string) (*current.IBPOrderer, error) { 282 jsonBytes, err := ConvertYamlFileToJson(file) 283 if err != nil { 284 return nil, err 285 } 286 287 on := ¤t.IBPOrderer{} 288 err = json.Unmarshal(jsonBytes, &on) 289 if err != nil { 290 return nil, err 291 } 292 293 return on, nil 294 } 295 296 func ConvertYamlFileToJson(file string) ([]byte, error) { 297 absfilepath, err := filepath.Abs(file) 298 if err != nil { 299 return nil, err 300 } 301 bytes, err := ioutil.ReadFile(filepath.Clean(absfilepath)) 302 if err != nil { 303 return nil, err 304 } 305 306 return yaml.ToJSON(bytes) 307 }