github.com/walkingsparrow/docker@v1.4.2-0.20151218153551-b708a2249bfa/daemon/logger/awslogs/cloudwatchlogs_test.go (about)

     1  package awslogs
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net/http"
     7  	"runtime"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/aws/aws-sdk-go/aws"
    13  	"github.com/aws/aws-sdk-go/aws/awserr"
    14  	"github.com/aws/aws-sdk-go/aws/request"
    15  	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
    16  	"github.com/docker/docker/daemon/logger"
    17  	"github.com/docker/docker/dockerversion"
    18  )
    19  
    20  const (
    21  	groupName         = "groupName"
    22  	streamName        = "streamName"
    23  	sequenceToken     = "sequenceToken"
    24  	nextSequenceToken = "nextSequenceToken"
    25  	logline           = "this is a log line"
    26  )
    27  
    28  func TestNewAWSLogsClientUserAgentHandler(t *testing.T) {
    29  	ctx := logger.Context{
    30  		Config: map[string]string{
    31  			regionKey: "us-east-1",
    32  		},
    33  	}
    34  
    35  	client, err := newAWSLogsClient(ctx)
    36  	if err != nil {
    37  		t.Fatal(err)
    38  	}
    39  	realClient, ok := client.(*cloudwatchlogs.CloudWatchLogs)
    40  	if !ok {
    41  		t.Fatal("Could not cast client to cloudwatchlogs.CloudWatchLogs")
    42  	}
    43  	buildHandlerList := realClient.Handlers.Build
    44  	request := &request.Request{
    45  		HTTPRequest: &http.Request{
    46  			Header: http.Header{},
    47  		},
    48  	}
    49  	buildHandlerList.Run(request)
    50  	expectedUserAgentString := fmt.Sprintf("Docker %s (%s) %s/%s",
    51  		dockerversion.Version, runtime.GOOS, aws.SDKName, aws.SDKVersion)
    52  	userAgent := request.HTTPRequest.Header.Get("User-Agent")
    53  	if userAgent != expectedUserAgentString {
    54  		t.Errorf("Wrong User-Agent string, expected \"%s\" but was \"%s\"",
    55  			expectedUserAgentString, userAgent)
    56  	}
    57  }
    58  
    59  func TestNewAWSLogsClientRegionDetect(t *testing.T) {
    60  	ctx := logger.Context{
    61  		Config: map[string]string{},
    62  	}
    63  
    64  	mockMetadata := newMockMetadataClient()
    65  	newRegionFinder = func() regionFinder {
    66  		return mockMetadata
    67  	}
    68  	mockMetadata.regionResult <- &regionResult{
    69  		successResult: "us-east-1",
    70  	}
    71  
    72  	_, err := newAWSLogsClient(ctx)
    73  	if err != nil {
    74  		t.Fatal(err)
    75  	}
    76  }
    77  
    78  func TestCreateSuccess(t *testing.T) {
    79  	mockClient := newMockClient()
    80  	stream := &logStream{
    81  		client:        mockClient,
    82  		logGroupName:  groupName,
    83  		logStreamName: streamName,
    84  	}
    85  	mockClient.createLogStreamResult <- &createLogStreamResult{}
    86  
    87  	err := stream.create()
    88  
    89  	if err != nil {
    90  		t.Errorf("Received unexpected err: %v\n", err)
    91  	}
    92  	argument := <-mockClient.createLogStreamArgument
    93  	if argument.LogGroupName == nil {
    94  		t.Fatal("Expected non-nil LogGroupName")
    95  	}
    96  	if *argument.LogGroupName != groupName {
    97  		t.Errorf("Expected LogGroupName to be %s", groupName)
    98  	}
    99  	if argument.LogStreamName == nil {
   100  		t.Fatal("Expected non-nil LogGroupName")
   101  	}
   102  	if *argument.LogStreamName != streamName {
   103  		t.Errorf("Expected LogStreamName to be %s", streamName)
   104  	}
   105  }
   106  
   107  func TestCreateError(t *testing.T) {
   108  	mockClient := newMockClient()
   109  	stream := &logStream{
   110  		client: mockClient,
   111  	}
   112  	mockClient.createLogStreamResult <- &createLogStreamResult{
   113  		errorResult: errors.New("Error!"),
   114  	}
   115  
   116  	err := stream.create()
   117  
   118  	if err == nil {
   119  		t.Fatal("Expected non-nil err")
   120  	}
   121  }
   122  
   123  func TestCreateAlreadyExists(t *testing.T) {
   124  	mockClient := newMockClient()
   125  	stream := &logStream{
   126  		client: mockClient,
   127  	}
   128  	mockClient.createLogStreamResult <- &createLogStreamResult{
   129  		errorResult: awserr.New(resourceAlreadyExistsCode, "", nil),
   130  	}
   131  
   132  	err := stream.create()
   133  
   134  	if err != nil {
   135  		t.Fatal("Expected nil err")
   136  	}
   137  }
   138  
   139  func TestPublishBatchSuccess(t *testing.T) {
   140  	mockClient := newMockClient()
   141  	stream := &logStream{
   142  		client:        mockClient,
   143  		logGroupName:  groupName,
   144  		logStreamName: streamName,
   145  		sequenceToken: aws.String(sequenceToken),
   146  	}
   147  	mockClient.putLogEventsResult <- &putLogEventsResult{
   148  		successResult: &cloudwatchlogs.PutLogEventsOutput{
   149  			NextSequenceToken: aws.String(nextSequenceToken),
   150  		},
   151  	}
   152  
   153  	events := []*cloudwatchlogs.InputLogEvent{
   154  		{
   155  			Message: aws.String(logline),
   156  		},
   157  	}
   158  
   159  	stream.publishBatch(events)
   160  	if stream.sequenceToken == nil {
   161  		t.Fatal("Expected non-nil sequenceToken")
   162  	}
   163  	if *stream.sequenceToken != nextSequenceToken {
   164  		t.Errorf("Expected sequenceToken to be %s, but was %s", nextSequenceToken, *stream.sequenceToken)
   165  	}
   166  	argument := <-mockClient.putLogEventsArgument
   167  	if argument == nil {
   168  		t.Fatal("Expected non-nil PutLogEventsInput")
   169  	}
   170  	if argument.SequenceToken == nil {
   171  		t.Fatal("Expected non-nil PutLogEventsInput.SequenceToken")
   172  	}
   173  	if *argument.SequenceToken != sequenceToken {
   174  		t.Errorf("Expected PutLogEventsInput.SequenceToken to be %s, but was %s", sequenceToken, *argument.SequenceToken)
   175  	}
   176  	if len(argument.LogEvents) != 1 {
   177  		t.Errorf("Expected LogEvents to contain 1 element, but contains %d", len(argument.LogEvents))
   178  	}
   179  	if argument.LogEvents[0] != events[0] {
   180  		t.Error("Expected event to equal input")
   181  	}
   182  }
   183  
   184  func TestPublishBatchError(t *testing.T) {
   185  	mockClient := newMockClient()
   186  	stream := &logStream{
   187  		client:        mockClient,
   188  		logGroupName:  groupName,
   189  		logStreamName: streamName,
   190  		sequenceToken: aws.String(sequenceToken),
   191  	}
   192  	mockClient.putLogEventsResult <- &putLogEventsResult{
   193  		errorResult: errors.New("Error!"),
   194  	}
   195  
   196  	events := []*cloudwatchlogs.InputLogEvent{
   197  		{
   198  			Message: aws.String(logline),
   199  		},
   200  	}
   201  
   202  	stream.publishBatch(events)
   203  	if stream.sequenceToken == nil {
   204  		t.Fatal("Expected non-nil sequenceToken")
   205  	}
   206  	if *stream.sequenceToken != sequenceToken {
   207  		t.Errorf("Expected sequenceToken to be %s, but was %s", sequenceToken, *stream.sequenceToken)
   208  	}
   209  }
   210  
   211  func TestPublishBatchInvalidSeqSuccess(t *testing.T) {
   212  	mockClient := newMockClientBuffered(2)
   213  	stream := &logStream{
   214  		client:        mockClient,
   215  		logGroupName:  groupName,
   216  		logStreamName: streamName,
   217  		sequenceToken: aws.String(sequenceToken),
   218  	}
   219  	mockClient.putLogEventsResult <- &putLogEventsResult{
   220  		errorResult: awserr.New(invalidSequenceTokenCode, "use token token", nil),
   221  	}
   222  	mockClient.putLogEventsResult <- &putLogEventsResult{
   223  		successResult: &cloudwatchlogs.PutLogEventsOutput{
   224  			NextSequenceToken: aws.String(nextSequenceToken),
   225  		},
   226  	}
   227  
   228  	events := []*cloudwatchlogs.InputLogEvent{
   229  		{
   230  			Message: aws.String(logline),
   231  		},
   232  	}
   233  
   234  	stream.publishBatch(events)
   235  	if stream.sequenceToken == nil {
   236  		t.Fatal("Expected non-nil sequenceToken")
   237  	}
   238  	if *stream.sequenceToken != nextSequenceToken {
   239  		t.Errorf("Expected sequenceToken to be %s, but was %s", nextSequenceToken, *stream.sequenceToken)
   240  	}
   241  
   242  	argument := <-mockClient.putLogEventsArgument
   243  	if argument == nil {
   244  		t.Fatal("Expected non-nil PutLogEventsInput")
   245  	}
   246  	if argument.SequenceToken == nil {
   247  		t.Fatal("Expected non-nil PutLogEventsInput.SequenceToken")
   248  	}
   249  	if *argument.SequenceToken != sequenceToken {
   250  		t.Errorf("Expected PutLogEventsInput.SequenceToken to be %s, but was %s", sequenceToken, *argument.SequenceToken)
   251  	}
   252  	if len(argument.LogEvents) != 1 {
   253  		t.Errorf("Expected LogEvents to contain 1 element, but contains %d", len(argument.LogEvents))
   254  	}
   255  	if argument.LogEvents[0] != events[0] {
   256  		t.Error("Expected event to equal input")
   257  	}
   258  
   259  	argument = <-mockClient.putLogEventsArgument
   260  	if argument == nil {
   261  		t.Fatal("Expected non-nil PutLogEventsInput")
   262  	}
   263  	if argument.SequenceToken == nil {
   264  		t.Fatal("Expected non-nil PutLogEventsInput.SequenceToken")
   265  	}
   266  	if *argument.SequenceToken != "token" {
   267  		t.Errorf("Expected PutLogEventsInput.SequenceToken to be %s, but was %s", "token", *argument.SequenceToken)
   268  	}
   269  	if len(argument.LogEvents) != 1 {
   270  		t.Errorf("Expected LogEvents to contain 1 element, but contains %d", len(argument.LogEvents))
   271  	}
   272  	if argument.LogEvents[0] != events[0] {
   273  		t.Error("Expected event to equal input")
   274  	}
   275  }
   276  
   277  func TestPublishBatchAlreadyAccepted(t *testing.T) {
   278  	mockClient := newMockClient()
   279  	stream := &logStream{
   280  		client:        mockClient,
   281  		logGroupName:  groupName,
   282  		logStreamName: streamName,
   283  		sequenceToken: aws.String(sequenceToken),
   284  	}
   285  	mockClient.putLogEventsResult <- &putLogEventsResult{
   286  		errorResult: awserr.New(dataAlreadyAcceptedCode, "use token token", nil),
   287  	}
   288  
   289  	events := []*cloudwatchlogs.InputLogEvent{
   290  		{
   291  			Message: aws.String(logline),
   292  		},
   293  	}
   294  
   295  	stream.publishBatch(events)
   296  	if stream.sequenceToken == nil {
   297  		t.Fatal("Expected non-nil sequenceToken")
   298  	}
   299  	if *stream.sequenceToken != "token" {
   300  		t.Errorf("Expected sequenceToken to be %s, but was %s", "token", *stream.sequenceToken)
   301  	}
   302  
   303  	argument := <-mockClient.putLogEventsArgument
   304  	if argument == nil {
   305  		t.Fatal("Expected non-nil PutLogEventsInput")
   306  	}
   307  	if argument.SequenceToken == nil {
   308  		t.Fatal("Expected non-nil PutLogEventsInput.SequenceToken")
   309  	}
   310  	if *argument.SequenceToken != sequenceToken {
   311  		t.Errorf("Expected PutLogEventsInput.SequenceToken to be %s, but was %s", sequenceToken, *argument.SequenceToken)
   312  	}
   313  	if len(argument.LogEvents) != 1 {
   314  		t.Errorf("Expected LogEvents to contain 1 element, but contains %d", len(argument.LogEvents))
   315  	}
   316  	if argument.LogEvents[0] != events[0] {
   317  		t.Error("Expected event to equal input")
   318  	}
   319  }
   320  
   321  func TestCollectBatchSimple(t *testing.T) {
   322  	mockClient := newMockClient()
   323  	stream := &logStream{
   324  		client:        mockClient,
   325  		logGroupName:  groupName,
   326  		logStreamName: streamName,
   327  		sequenceToken: aws.String(sequenceToken),
   328  		messages:      make(chan *logger.Message),
   329  	}
   330  	mockClient.putLogEventsResult <- &putLogEventsResult{
   331  		successResult: &cloudwatchlogs.PutLogEventsOutput{
   332  			NextSequenceToken: aws.String(nextSequenceToken),
   333  		},
   334  	}
   335  	ticks := make(chan time.Time)
   336  	newTicker = func(_ time.Duration) *time.Ticker {
   337  		return &time.Ticker{
   338  			C: ticks,
   339  		}
   340  	}
   341  
   342  	go stream.collectBatch()
   343  
   344  	stream.Log(&logger.Message{
   345  		Line:      []byte(logline),
   346  		Timestamp: time.Time{},
   347  	})
   348  
   349  	ticks <- time.Time{}
   350  	stream.Close()
   351  
   352  	argument := <-mockClient.putLogEventsArgument
   353  	if argument == nil {
   354  		t.Fatal("Expected non-nil PutLogEventsInput")
   355  	}
   356  	if len(argument.LogEvents) != 1 {
   357  		t.Errorf("Expected LogEvents to contain 1 element, but contains %d", len(argument.LogEvents))
   358  	}
   359  	if *argument.LogEvents[0].Message != logline {
   360  		t.Errorf("Expected message to be %s but was %s", logline, *argument.LogEvents[0].Message)
   361  	}
   362  }
   363  
   364  func TestCollectBatchTicker(t *testing.T) {
   365  	mockClient := newMockClient()
   366  	stream := &logStream{
   367  		client:        mockClient,
   368  		logGroupName:  groupName,
   369  		logStreamName: streamName,
   370  		sequenceToken: aws.String(sequenceToken),
   371  		messages:      make(chan *logger.Message),
   372  	}
   373  	mockClient.putLogEventsResult <- &putLogEventsResult{
   374  		successResult: &cloudwatchlogs.PutLogEventsOutput{
   375  			NextSequenceToken: aws.String(nextSequenceToken),
   376  		},
   377  	}
   378  	ticks := make(chan time.Time)
   379  	newTicker = func(_ time.Duration) *time.Ticker {
   380  		return &time.Ticker{
   381  			C: ticks,
   382  		}
   383  	}
   384  
   385  	go stream.collectBatch()
   386  
   387  	stream.Log(&logger.Message{
   388  		Line:      []byte(logline + " 1"),
   389  		Timestamp: time.Time{},
   390  	})
   391  	stream.Log(&logger.Message{
   392  		Line:      []byte(logline + " 2"),
   393  		Timestamp: time.Time{},
   394  	})
   395  
   396  	ticks <- time.Time{}
   397  
   398  	// Verify first batch
   399  	argument := <-mockClient.putLogEventsArgument
   400  	if argument == nil {
   401  		t.Fatal("Expected non-nil PutLogEventsInput")
   402  	}
   403  	if len(argument.LogEvents) != 2 {
   404  		t.Errorf("Expected LogEvents to contain 2 elements, but contains %d", len(argument.LogEvents))
   405  	}
   406  	if *argument.LogEvents[0].Message != logline+" 1" {
   407  		t.Errorf("Expected message to be %s but was %s", logline+" 1", *argument.LogEvents[0].Message)
   408  	}
   409  	if *argument.LogEvents[1].Message != logline+" 2" {
   410  		t.Errorf("Expected message to be %s but was %s", logline+" 2", *argument.LogEvents[0].Message)
   411  	}
   412  
   413  	stream.Log(&logger.Message{
   414  		Line:      []byte(logline + " 3"),
   415  		Timestamp: time.Time{},
   416  	})
   417  
   418  	ticks <- time.Time{}
   419  	argument = <-mockClient.putLogEventsArgument
   420  	if argument == nil {
   421  		t.Fatal("Expected non-nil PutLogEventsInput")
   422  	}
   423  	if len(argument.LogEvents) != 1 {
   424  		t.Errorf("Expected LogEvents to contain 1 elements, but contains %d", len(argument.LogEvents))
   425  	}
   426  	if *argument.LogEvents[0].Message != logline+" 3" {
   427  		t.Errorf("Expected message to be %s but was %s", logline+" 3", *argument.LogEvents[0].Message)
   428  	}
   429  
   430  	stream.Close()
   431  
   432  }
   433  
   434  func TestCollectBatchClose(t *testing.T) {
   435  	mockClient := newMockClient()
   436  	stream := &logStream{
   437  		client:        mockClient,
   438  		logGroupName:  groupName,
   439  		logStreamName: streamName,
   440  		sequenceToken: aws.String(sequenceToken),
   441  		messages:      make(chan *logger.Message),
   442  	}
   443  	mockClient.putLogEventsResult <- &putLogEventsResult{
   444  		successResult: &cloudwatchlogs.PutLogEventsOutput{
   445  			NextSequenceToken: aws.String(nextSequenceToken),
   446  		},
   447  	}
   448  	var ticks = make(chan time.Time)
   449  	newTicker = func(_ time.Duration) *time.Ticker {
   450  		return &time.Ticker{
   451  			C: ticks,
   452  		}
   453  	}
   454  
   455  	go stream.collectBatch()
   456  
   457  	stream.Log(&logger.Message{
   458  		Line:      []byte(logline),
   459  		Timestamp: time.Time{},
   460  	})
   461  
   462  	// no ticks
   463  	stream.Close()
   464  
   465  	argument := <-mockClient.putLogEventsArgument
   466  	if argument == nil {
   467  		t.Fatal("Expected non-nil PutLogEventsInput")
   468  	}
   469  	if len(argument.LogEvents) != 1 {
   470  		t.Errorf("Expected LogEvents to contain 1 element, but contains %d", len(argument.LogEvents))
   471  	}
   472  	if *argument.LogEvents[0].Message != logline {
   473  		t.Errorf("Expected message to be %s but was %s", logline, *argument.LogEvents[0].Message)
   474  	}
   475  }
   476  
   477  func TestCollectBatchLineSplit(t *testing.T) {
   478  	mockClient := newMockClient()
   479  	stream := &logStream{
   480  		client:        mockClient,
   481  		logGroupName:  groupName,
   482  		logStreamName: streamName,
   483  		sequenceToken: aws.String(sequenceToken),
   484  		messages:      make(chan *logger.Message),
   485  	}
   486  	mockClient.putLogEventsResult <- &putLogEventsResult{
   487  		successResult: &cloudwatchlogs.PutLogEventsOutput{
   488  			NextSequenceToken: aws.String(nextSequenceToken),
   489  		},
   490  	}
   491  	var ticks = make(chan time.Time)
   492  	newTicker = func(_ time.Duration) *time.Ticker {
   493  		return &time.Ticker{
   494  			C: ticks,
   495  		}
   496  	}
   497  
   498  	go stream.collectBatch()
   499  
   500  	longline := strings.Repeat("A", maximumBytesPerEvent)
   501  	stream.Log(&logger.Message{
   502  		Line:      []byte(longline + "B"),
   503  		Timestamp: time.Time{},
   504  	})
   505  
   506  	// no ticks
   507  	stream.Close()
   508  
   509  	argument := <-mockClient.putLogEventsArgument
   510  	if argument == nil {
   511  		t.Fatal("Expected non-nil PutLogEventsInput")
   512  	}
   513  	if len(argument.LogEvents) != 2 {
   514  		t.Errorf("Expected LogEvents to contain 2 elements, but contains %d", len(argument.LogEvents))
   515  	}
   516  	if *argument.LogEvents[0].Message != longline {
   517  		t.Errorf("Expected message to be %s but was %s", longline, *argument.LogEvents[0].Message)
   518  	}
   519  	if *argument.LogEvents[1].Message != "B" {
   520  		t.Errorf("Expected message to be %s but was %s", "B", *argument.LogEvents[1].Message)
   521  	}
   522  }
   523  
   524  func TestCollectBatchMaxEvents(t *testing.T) {
   525  	mockClient := newMockClientBuffered(1)
   526  	stream := &logStream{
   527  		client:        mockClient,
   528  		logGroupName:  groupName,
   529  		logStreamName: streamName,
   530  		sequenceToken: aws.String(sequenceToken),
   531  		messages:      make(chan *logger.Message),
   532  	}
   533  	mockClient.putLogEventsResult <- &putLogEventsResult{
   534  		successResult: &cloudwatchlogs.PutLogEventsOutput{
   535  			NextSequenceToken: aws.String(nextSequenceToken),
   536  		},
   537  	}
   538  	var ticks = make(chan time.Time)
   539  	newTicker = func(_ time.Duration) *time.Ticker {
   540  		return &time.Ticker{
   541  			C: ticks,
   542  		}
   543  	}
   544  
   545  	go stream.collectBatch()
   546  
   547  	line := "A"
   548  	for i := 0; i <= maximumLogEventsPerPut; i++ {
   549  		stream.Log(&logger.Message{
   550  			Line:      []byte(line),
   551  			Timestamp: time.Time{},
   552  		})
   553  	}
   554  
   555  	// no ticks
   556  	stream.Close()
   557  
   558  	argument := <-mockClient.putLogEventsArgument
   559  	if argument == nil {
   560  		t.Fatal("Expected non-nil PutLogEventsInput")
   561  	}
   562  	if len(argument.LogEvents) != maximumLogEventsPerPut {
   563  		t.Errorf("Expected LogEvents to contain %d elements, but contains %d", maximumLogEventsPerPut, len(argument.LogEvents))
   564  	}
   565  
   566  	argument = <-mockClient.putLogEventsArgument
   567  	if argument == nil {
   568  		t.Fatal("Expected non-nil PutLogEventsInput")
   569  	}
   570  	if len(argument.LogEvents) != 1 {
   571  		t.Errorf("Expected LogEvents to contain %d elements, but contains %d", 1, len(argument.LogEvents))
   572  	}
   573  }
   574  
   575  func TestCollectBatchMaxTotalBytes(t *testing.T) {
   576  	mockClient := newMockClientBuffered(1)
   577  	stream := &logStream{
   578  		client:        mockClient,
   579  		logGroupName:  groupName,
   580  		logStreamName: streamName,
   581  		sequenceToken: aws.String(sequenceToken),
   582  		messages:      make(chan *logger.Message),
   583  	}
   584  	mockClient.putLogEventsResult <- &putLogEventsResult{
   585  		successResult: &cloudwatchlogs.PutLogEventsOutput{
   586  			NextSequenceToken: aws.String(nextSequenceToken),
   587  		},
   588  	}
   589  	var ticks = make(chan time.Time)
   590  	newTicker = func(_ time.Duration) *time.Ticker {
   591  		return &time.Ticker{
   592  			C: ticks,
   593  		}
   594  	}
   595  
   596  	go stream.collectBatch()
   597  
   598  	longline := strings.Repeat("A", maximumBytesPerPut)
   599  	stream.Log(&logger.Message{
   600  		Line:      []byte(longline + "B"),
   601  		Timestamp: time.Time{},
   602  	})
   603  
   604  	// no ticks
   605  	stream.Close()
   606  
   607  	argument := <-mockClient.putLogEventsArgument
   608  	if argument == nil {
   609  		t.Fatal("Expected non-nil PutLogEventsInput")
   610  	}
   611  	bytes := 0
   612  	for _, event := range argument.LogEvents {
   613  		bytes += len(*event.Message)
   614  	}
   615  	if bytes > maximumBytesPerPut {
   616  		t.Errorf("Expected <= %d bytes but was %d", maximumBytesPerPut, bytes)
   617  	}
   618  
   619  	argument = <-mockClient.putLogEventsArgument
   620  	if len(argument.LogEvents) != 1 {
   621  		t.Errorf("Expected LogEvents to contain 1 elements, but contains %d", len(argument.LogEvents))
   622  	}
   623  	message := *argument.LogEvents[0].Message
   624  	if message[len(message)-1:] != "B" {
   625  		t.Errorf("Expected message to be %s but was %s", "B", message[len(message)-1:])
   626  	}
   627  }