github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/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  }