github.com/TrueCloudLab/frostfs-api-go/v2@v2.0.0-20230228134343-196241c4e79a/netmap/attributes.go (about) 1 package netmap 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 8 "github.com/TrueCloudLab/frostfs-api-go/v2/refs" 9 ) 10 11 // prefix of keys to subnet attributes. 12 const attrSubnetPrefix = "__NEOFS__SUBNET_" 13 14 const ( 15 // subnet attribute's value denoting subnet entry 16 attrSubnetValEntry = "True" 17 18 // subnet attribute's value denoting subnet exit 19 attrSubnetValExit = "False" 20 ) 21 22 // NodeSubnetInfo groups information about subnet which can be written to NodeInfo. 23 // 24 // Zero value represents entry to zero subnet. 25 type NodeSubnetInfo struct { 26 exit bool 27 28 id *refs.SubnetID 29 } 30 31 // Enabled returns true iff subnet membership is enabled for the node. 32 func (x NodeSubnetInfo) Enabled() bool { 33 return !x.exit 34 } 35 36 // SetEntryFlag sets the subnet entry flag. 37 func (x *NodeSubnetInfo) SetEntryFlag(enters bool) { 38 x.exit = !enters 39 } 40 41 // ID returns identifier of the subnet. 42 func (x NodeSubnetInfo) ID() *refs.SubnetID { 43 return x.id 44 } 45 46 // SetID sets identifier of the subnet. 47 func (x *NodeSubnetInfo) SetID(id *refs.SubnetID) { 48 x.id = id 49 } 50 51 func subnetAttributeKey(id *refs.SubnetID) string { 52 txt, _ := id.MarshalText() // never returns an error 53 54 return attrSubnetPrefix + string(txt) 55 } 56 57 // WriteSubnetInfo writes NodeSubnetInfo to NodeInfo via attributes. NodeInfo must not be nil. 58 // 59 // Existing subnet attributes are expected to be key-unique, otherwise undefined behavior. 60 // 61 // Does not add (removes existing) attribute if node: 62 // - disables non-zero subnet; 63 // - enables zero subnet. 64 // 65 // Attribute key is calculated from ID using format `__NEOFS__SUBNET_%s`. 66 // Attribute Value is: 67 // - `True` if node enters the subnet; 68 // - `False`, otherwise. 69 func WriteSubnetInfo(node *NodeInfo, info NodeSubnetInfo) { 70 attrs := node.GetAttributes() 71 72 id := info.ID() 73 enters := info.Enabled() 74 75 // calculate attribute key 76 key := subnetAttributeKey(id) 77 78 if refs.IsZeroSubnet(id) == enters { 79 for i := range attrs { 80 if attrs[i].GetKey() == key { 81 attrs = append(attrs[:i], attrs[i+1:]...) 82 break // attributes are expected to be key-unique 83 } 84 } 85 } else { 86 var val string 87 88 if enters { 89 val = attrSubnetValEntry 90 } else { 91 val = attrSubnetValExit 92 } 93 94 presented := false 95 96 for i := range attrs { 97 if attrs[i].GetKey() == key { 98 attrs[i].SetValue(val) 99 presented = true 100 } 101 } 102 103 if !presented { 104 index := len(attrs) 105 attrs = append(attrs, Attribute{}) 106 attrs[index].SetKey(key) 107 attrs[index].SetValue(val) 108 } 109 } 110 111 node.SetAttributes(attrs) 112 } 113 114 // ErrRemoveSubnet is returned when a node needs to leave the subnet. 115 var ErrRemoveSubnet = errors.New("remove subnet") 116 117 var errNoSubnets = errors.New("no subnets") 118 119 // IterateSubnets iterates over all subnets the node belongs to and passes the IDs to f. 120 // Handler must not be nil. 121 // 122 // Subnet attributes are expected to be key-unique, otherwise undefined behavior. 123 // 124 // If f returns ErrRemoveSubnet, then removes subnet entry. Note that this leads to an instant mutation of NodeInfo. 125 // Breaks on any other non-nil error and returns it. 126 // 127 // Returns an error if any subnet attribute has wrong format. 128 // Returns an error if the node is not included in any subnet by the end of the loop. 129 func IterateSubnets(node *NodeInfo, f func(refs.SubnetID) error) error { 130 attrs := node.GetAttributes() 131 132 var ( 133 err error 134 id refs.SubnetID 135 entries uint 136 137 zeroEntry = true 138 ) 139 140 for i := 0; i < len(attrs); i++ { // range must not be used because of attrs mutation in body 141 key := attrs[i].GetKey() 142 143 // cut subnet ID string 144 idTxt := strings.TrimPrefix(key, attrSubnetPrefix) 145 if len(idTxt) == len(key) { 146 // not a subnet attribute 147 continue 148 } 149 150 // check value 151 val := attrs[i].GetValue() 152 if val != attrSubnetValExit && val != attrSubnetValEntry { 153 return fmt.Errorf("invalid attribute value: %s", val) 154 } 155 156 // decode subnet ID 157 if err = id.UnmarshalText([]byte(idTxt)); err != nil { 158 return fmt.Errorf("invalid ID text: %w", err) 159 } 160 161 // update status of zero subnet 162 isZero := refs.IsZeroSubnet(&id) 163 164 if isZero { 165 zeroEntry = val == attrSubnetValEntry 166 } 167 168 // continue to process only the subnets to which the node belongs 169 if val == attrSubnetValExit { 170 continue 171 } 172 173 // pass ID to the handler 174 err = f(id) 175 176 isRemoveErr := errors.Is(err, ErrRemoveSubnet) 177 178 if err != nil && !isRemoveErr { 179 return err 180 } 181 182 if isRemoveErr { 183 if isZero { 184 // we can't remove attribute of zero subnet because it means entry 185 attrs[i].SetValue(attrSubnetValExit) 186 } else { 187 // we can set False or remove attribute, latter is more memory/network efficient. 188 attrs = append(attrs[:i], attrs[i+1:]...) 189 i-- 190 } 191 192 continue 193 } 194 195 entries++ 196 } 197 198 if zeroEntry { 199 // missing attribute of zero subnet equivalent to entry 200 refs.MakeZeroSubnet(&id) 201 202 err = f(id) 203 if err != nil { 204 if !errors.Is(err, ErrRemoveSubnet) { 205 return err 206 } 207 208 // zero subnet should be clearly removed with False value 209 index := len(attrs) 210 attrs = append(attrs, Attribute{}) 211 attrs[index].SetKey(subnetAttributeKey(&id)) 212 attrs[index].SetValue(attrSubnetValExit) 213 } else { 214 entries++ 215 } 216 } 217 218 if entries <= 0 { 219 return errNoSubnets 220 } 221 222 node.SetAttributes(attrs) 223 224 return nil 225 }