github.com/m3db/m3@v1.5.0/src/msg/topic/topic.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package topic 22 23 import ( 24 "bytes" 25 "errors" 26 "fmt" 27 "time" 28 29 "github.com/m3db/m3/src/cluster/kv" 30 "github.com/m3db/m3/src/cluster/services" 31 "github.com/m3db/m3/src/msg/generated/proto/topicpb" 32 ) 33 34 var ( 35 errEmptyName = errors.New("invalid topic: empty name") 36 errZeroShards = errors.New("invalid topic: zero shards") 37 ) 38 39 type topic struct { 40 name string 41 numOfShards uint32 42 consumerServices []ConsumerService 43 version int 44 } 45 46 // NewTopic creates a new topic. 47 func NewTopic() Topic { return new(topic) } 48 49 // NewTopicFromValue creates a topic from a kv.Value. 50 func NewTopicFromValue(v kv.Value) (Topic, error) { 51 var topic topicpb.Topic 52 if err := v.Unmarshal(&topic); err != nil { 53 return nil, err 54 } 55 t, err := NewTopicFromProto(&topic) 56 if err != nil { 57 return nil, err 58 } 59 return t.SetVersion(v.Version()), nil 60 } 61 62 // NewTopicFromProto creates a topic from a proto. 63 func NewTopicFromProto(t *topicpb.Topic) (Topic, error) { 64 css := make([]ConsumerService, len(t.ConsumerServices)) 65 for i, cspb := range t.ConsumerServices { 66 cs, err := NewConsumerServiceFromProto(cspb) 67 if err != nil { 68 return nil, err 69 } 70 css[i] = cs 71 } 72 return NewTopic(). 73 SetName(t.Name). 74 SetNumberOfShards(t.NumberOfShards). 75 SetConsumerServices(css), nil 76 } 77 78 func (t *topic) Name() string { 79 return t.name 80 } 81 82 func (t *topic) SetName(value string) Topic { 83 newt := *t 84 newt.name = value 85 return &newt 86 } 87 88 func (t *topic) NumberOfShards() uint32 { 89 return t.numOfShards 90 } 91 92 func (t *topic) SetNumberOfShards(value uint32) Topic { 93 newt := *t 94 newt.numOfShards = value 95 return &newt 96 } 97 98 func (t *topic) ConsumerServices() []ConsumerService { 99 return t.consumerServices 100 } 101 102 func (t *topic) SetConsumerServices(value []ConsumerService) Topic { 103 newt := *t 104 newt.consumerServices = value 105 return &newt 106 } 107 108 func (t *topic) Version() int { 109 return t.version 110 } 111 112 func (t *topic) SetVersion(value int) Topic { 113 newt := *t 114 newt.version = value 115 return &newt 116 } 117 118 func (t *topic) AddConsumerService(value ConsumerService) (Topic, error) { 119 cur := t.ConsumerServices() 120 for _, cs := range cur { 121 if cs.ServiceID().Equal(value.ServiceID()) { 122 return nil, fmt.Errorf("service %s is already consuming the topic", value.ServiceID().String()) 123 } 124 } 125 126 return t.SetConsumerServices(append(cur, value)), nil 127 } 128 129 func (t *topic) RemoveConsumerService(value services.ServiceID) (Topic, error) { 130 cur := t.ConsumerServices() 131 for i, cs := range cur { 132 if cs.ServiceID().Equal(value) { 133 cur = append(cur[:i], cur[i+1:]...) 134 return t.SetConsumerServices(cur), nil 135 } 136 } 137 138 return nil, fmt.Errorf("could not find consumer service %s in the topic", value.String()) 139 } 140 141 func (t *topic) UpdateConsumerService(value ConsumerService) (Topic, error) { 142 css := t.ConsumerServices() 143 for i, cs := range css { 144 if !cs.ServiceID().Equal(value.ServiceID()) { 145 continue 146 } 147 if value.ConsumptionType() != cs.ConsumptionType() { 148 return nil, fmt.Errorf("could not change consumption type for consumer service %s", value.ServiceID().String()) 149 } 150 css[i] = value 151 return t.SetConsumerServices(css), nil 152 } 153 return nil, fmt.Errorf("could not find consumer service %s in the topic", value.String()) 154 } 155 156 func (t *topic) String() string { 157 var buf bytes.Buffer 158 buf.WriteString("\n{\n") 159 buf.WriteString(fmt.Sprintf("\tversion: %d\n", t.version)) 160 buf.WriteString(fmt.Sprintf("\tname: %s\n", t.name)) 161 buf.WriteString(fmt.Sprintf("\tnumOfShards: %d\n", t.numOfShards)) 162 if len(t.consumerServices) > 0 { 163 buf.WriteString("\tconsumerServices: {\n") 164 } 165 for _, cs := range t.consumerServices { 166 buf.WriteString(fmt.Sprintf("\t\t%s\n", cs.String())) 167 } 168 if len(t.consumerServices) > 0 { 169 buf.WriteString("\t}\n") 170 } 171 buf.WriteString("}\n") 172 return buf.String() 173 } 174 175 func (t *topic) Validate() error { 176 if t.Name() == "" { 177 return errEmptyName 178 } 179 if t.NumberOfShards() == 0 { 180 return errZeroShards 181 } 182 uniqConsumers := make(map[string]struct{}, len(t.ConsumerServices())) 183 for _, cs := range t.ConsumerServices() { 184 _, ok := uniqConsumers[cs.ServiceID().String()] 185 if ok { 186 return fmt.Errorf("invalid topic: duplicated consumer %s", cs.ServiceID().String()) 187 } 188 uniqConsumers[cs.ServiceID().String()] = struct{}{} 189 } 190 return nil 191 } 192 193 // ToProto creates proto from a topic. 194 func ToProto(t Topic) (*topicpb.Topic, error) { 195 css := t.ConsumerServices() 196 csspb := make([]*topicpb.ConsumerService, len(css)) 197 for i, cs := range css { 198 cspb, err := ConsumerServiceToProto(cs) 199 if err != nil { 200 return nil, err 201 } 202 csspb[i] = cspb 203 } 204 return &topicpb.Topic{ 205 Name: t.Name(), 206 NumberOfShards: t.NumberOfShards(), 207 ConsumerServices: csspb, 208 }, nil 209 } 210 211 type consumerService struct { 212 sid services.ServiceID 213 ct ConsumptionType 214 ttlNanos int64 215 } 216 217 // NewConsumerService creates a ConsumerService. 218 func NewConsumerService() ConsumerService { 219 return new(consumerService) 220 } 221 222 // NewConsumerServiceFromProto creates a ConsumerService from a proto. 223 func NewConsumerServiceFromProto(cs *topicpb.ConsumerService) (ConsumerService, error) { 224 ct, err := NewConsumptionTypeFromProto(cs.ConsumptionType) 225 if err != nil { 226 return nil, err 227 } 228 return NewConsumerService(). 229 SetServiceID(NewServiceIDFromProto(cs.ServiceId)). 230 SetConsumptionType(ct). 231 SetMessageTTLNanos(cs.MessageTtlNanos), nil 232 } 233 234 // ConsumerServiceToProto creates proto from a ConsumerService. 235 func ConsumerServiceToProto(cs ConsumerService) (*topicpb.ConsumerService, error) { 236 ct, err := ConsumptionTypeToProto(cs.ConsumptionType()) 237 if err != nil { 238 return nil, err 239 } 240 return &topicpb.ConsumerService{ 241 ConsumptionType: ct, 242 ServiceId: ServiceIDToProto(cs.ServiceID()), 243 MessageTtlNanos: cs.MessageTTLNanos(), 244 }, nil 245 } 246 247 func (cs *consumerService) ServiceID() services.ServiceID { 248 return cs.sid 249 } 250 251 func (cs *consumerService) SetServiceID(value services.ServiceID) ConsumerService { 252 newcs := *cs 253 newcs.sid = value 254 return &newcs 255 } 256 257 func (cs *consumerService) ConsumptionType() ConsumptionType { 258 return cs.ct 259 } 260 261 func (cs *consumerService) SetConsumptionType(value ConsumptionType) ConsumerService { 262 newcs := *cs 263 newcs.ct = value 264 return &newcs 265 } 266 267 func (cs *consumerService) MessageTTLNanos() int64 { 268 return cs.ttlNanos 269 } 270 271 func (cs *consumerService) SetMessageTTLNanos(value int64) ConsumerService { 272 newcs := *cs 273 newcs.ttlNanos = value 274 return &newcs 275 } 276 277 func (cs *consumerService) String() string { 278 var buf bytes.Buffer 279 buf.WriteString("{") 280 buf.WriteString(fmt.Sprintf("service: %s, consumption type: %s", cs.sid.String(), cs.ct.String())) 281 if cs.ttlNanos != 0 { 282 buf.WriteString(fmt.Sprintf(", ttl: %v", time.Duration(cs.ttlNanos))) 283 } 284 buf.WriteString("}") 285 return buf.String() 286 } 287 288 // NewServiceIDFromProto creates service id from a proto. 289 func NewServiceIDFromProto(sid *topicpb.ServiceID) services.ServiceID { 290 return services.NewServiceID().SetName(sid.Name).SetEnvironment(sid.Environment).SetZone(sid.Zone) 291 } 292 293 // ServiceIDToProto creates proto from a service id. 294 func ServiceIDToProto(sid services.ServiceID) *topicpb.ServiceID { 295 return &topicpb.ServiceID{ 296 Name: sid.Name(), 297 Environment: sid.Environment(), 298 Zone: sid.Zone(), 299 } 300 }