github.com/kyma-project/kyma-environment-broker@v0.0.1/common/orchestration/client_test.go (about)

     1  package orchestration
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"reflect"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/kyma-project/kyma-environment-broker/common/pagination"
    18  	"github.com/stretchr/testify/assert"
    19  	"github.com/stretchr/testify/require"
    20  	"golang.org/x/exp/slices"
    21  	"golang.org/x/oauth2"
    22  )
    23  
    24  type FakeTokenSource string
    25  
    26  var fixToken FakeTokenSource = "fake-token-1234"
    27  
    28  func (t FakeTokenSource) Token() (*oauth2.Token, error) {
    29  	return &oauth2.Token{
    30  		AccessToken: string(t),
    31  		Expiry:      time.Now().Add(time.Duration(12 * time.Hour)),
    32  	}, nil
    33  }
    34  
    35  var orch1 = fixStatusResponse("orchestration1")
    36  var orch2 = fixStatusResponse("orchestration2")
    37  var orch3 = fixStatusResponse("orchestration3")
    38  var orch4 = fixStatusResponse("orchestration4")
    39  var orchs = []StatusResponse{orch1, orch2, orch3, orch4}
    40  var operations = []OperationResponse{
    41  	fixOperationResponse("operation1", orch1.OrchestrationID, "in progress"),
    42  	fixOperationResponse("operation2", orch1.OrchestrationID, "pending"),
    43  	fixOperationResponse("operation3", orch1.OrchestrationID, "in progress"),
    44  	fixOperationResponse("operation4", orch1.OrchestrationID, "pending"),
    45  }
    46  
    47  var operationsWithFailedState = []OperationResponse{
    48  	fixOperationResponse("operation1", orch1.OrchestrationID, "pending"),
    49  	fixOperationResponse("operation2", orch1.OrchestrationID, "in progress"),
    50  	fixOperationResponse("operation3", orch1.OrchestrationID, "failed"),
    51  	fixOperationResponse("operation4", orch1.OrchestrationID, "in progress"),
    52  }
    53  
    54  func TestClient_ListOrchestrations(t *testing.T) {
    55  	t.Run("test_URL_params_pagination__NoError_path", func(t *testing.T) {
    56  		// given
    57  		called := 0
    58  		params := ListParameters{
    59  			PageSize: 2,
    60  			States:   []string{"failed", "in progress"},
    61  		}
    62  		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    63  			called++
    64  			assert.Equal(t, http.MethodGet, r.Method)
    65  			assert.Equal(t, "/orchestrations", r.URL.Path)
    66  			assert.Equal(t, fmt.Sprintf("Bearer %s", fixToken), r.Header.Get("Authorization"))
    67  			query := r.URL.Query()
    68  			assert.ElementsMatch(t, []string{strconv.Itoa(called)}, query[pagination.PageParam])
    69  			assert.ElementsMatch(t, []string{strconv.Itoa(params.PageSize)}, query[pagination.PageSizeParam])
    70  			assert.ElementsMatch(t, params.States, query[StateParam])
    71  
    72  			err := respondStatusList(w, orchs[(called-1)*params.PageSize:called*params.PageSize], 4)
    73  			require.NoError(t, err)
    74  		}))
    75  		defer ts.Close()
    76  		client := NewClient(context.TODO(), ts.URL, fixToken)
    77  
    78  		// when
    79  		srl, err := client.ListOrchestrations(params)
    80  
    81  		// then
    82  		require.NoError(t, err)
    83  		assert.Equal(t, 2, called)
    84  		assert.Equal(t, 4, srl.Count)
    85  		assert.Equal(t, 4, srl.TotalCount)
    86  		assert.Len(t, srl.Data, 4)
    87  		assert.Equal(t, orch1.OrchestrationID, srl.Data[0].OrchestrationID)
    88  		assert.Equal(t, orch2.OrchestrationID, srl.Data[1].OrchestrationID)
    89  		assert.Equal(t, orch3.OrchestrationID, srl.Data[2].OrchestrationID)
    90  		assert.Equal(t, orch4.OrchestrationID, srl.Data[3].OrchestrationID)
    91  	})
    92  }
    93  
    94  func TestClient_GetOrchestration(t *testing.T) {
    95  	t.Run("test_URL__NoError_path", func(t *testing.T) {
    96  		// given
    97  		called := 0
    98  		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    99  			called++
   100  			assert.Equal(t, http.MethodGet, r.Method)
   101  			assert.Equal(t, fmt.Sprintf("/orchestrations/%s", orch1.OrchestrationID), r.URL.Path)
   102  			assert.Equal(t, fmt.Sprintf("Bearer %s", fixToken), r.Header.Get("Authorization"))
   103  
   104  			err := respondStatus(w, orch1)
   105  			require.NoError(t, err)
   106  		}))
   107  		defer ts.Close()
   108  		client := NewClient(context.TODO(), ts.URL, fixToken)
   109  
   110  		// when
   111  		sr, err := client.GetOrchestration(orch1.OrchestrationID)
   112  
   113  		// then
   114  		require.NoError(t, err)
   115  		assert.Equal(t, 1, called)
   116  		assert.Equal(t, orch1.OrchestrationID, sr.OrchestrationID)
   117  	})
   118  }
   119  
   120  func TestClient_ListOperationsWithoutFailed(t *testing.T) {
   121  	t.Run("test_URL_params_pagination__NoError_path", func(t *testing.T) {
   122  		// given
   123  		called := 0
   124  		params := ListParameters{
   125  			PageSize: 2,
   126  			States:   []string{"pending", "in progress"},
   127  		}
   128  		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   129  			called++
   130  			assert.Equal(t, http.MethodGet, r.Method)
   131  			assert.Equal(t, fmt.Sprintf("/orchestrations/%s/operations", orch1.OrchestrationID), r.URL.Path)
   132  			assert.Equal(t, fmt.Sprintf("Bearer %s", fixToken), r.Header.Get("Authorization"))
   133  			query := r.URL.Query()
   134  
   135  			assert.ElementsMatch(t, []string{strconv.Itoa(called)}, query[pagination.PageParam])
   136  			assert.ElementsMatch(t, []string{strconv.Itoa(params.PageSize)}, query[pagination.PageSizeParam])
   137  			assert.ElementsMatch(t, params.States, query[StateParam])
   138  
   139  			err := respondOperationList(w, operations[(called-1)*params.PageSize:called*params.PageSize], 4)
   140  			require.NoError(t, err)
   141  		}))
   142  		defer ts.Close()
   143  		client := NewClient(context.TODO(), ts.URL, fixToken)
   144  
   145  		// when
   146  		orl, err := client.ListOperations(orch1.OrchestrationID, params)
   147  
   148  		// then
   149  		require.NoError(t, err)
   150  		assert.Equal(t, 2, called)
   151  		assert.Equal(t, 4, orl.Count)
   152  		assert.Equal(t, 4, orl.TotalCount)
   153  		assert.Len(t, orl.Data, 4)
   154  		for i := 0; i < 4; i++ {
   155  			assert.Equal(t, orch1.OrchestrationID, orl.Data[i].OrchestrationID)
   156  			assert.Equal(t, operations[i].OperationID, orl.Data[i].OperationID)
   157  		}
   158  	})
   159  }
   160  
   161  func TestClient_ListOperationsWithFailed(t *testing.T) {
   162  	t.Run("test_URL_params_pagination__NoError_path", func(t *testing.T) {
   163  		// given
   164  		called := 0
   165  		params := ListParameters{
   166  			PageSize: 2,
   167  			States:   []string{"failed"},
   168  		}
   169  		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   170  			called++
   171  			assert.Equal(t, http.MethodGet, r.Method)
   172  			assert.Equal(t, fmt.Sprintf("/orchestrations/%s/operations", orch1.OrchestrationID), r.URL.Path)
   173  			assert.Equal(t, fmt.Sprintf("Bearer %s", fixToken), r.Header.Get("Authorization"))
   174  			query := r.URL.Query()
   175  			assert.ElementsMatch(t, params.States, query[StateParam])
   176  			err := respondOperationList(w, operations[(called-1)*params.PageSize:called*params.PageSize], 2)
   177  			require.NoError(t, err)
   178  		}))
   179  		defer ts.Close()
   180  		client := NewClient(context.TODO(), ts.URL, fixToken)
   181  		// when
   182  		orl, err := client.ListOperations(orch1.OrchestrationID, params)
   183  		// then
   184  		require.NoError(t, err)
   185  		assert.Equal(t, 1, called)
   186  		assert.Equal(t, 2, orl.Count)
   187  		assert.Equal(t, 2, orl.TotalCount)
   188  		assert.Len(t, orl.Data, 2)
   189  		for i := 0; i < 2; i++ {
   190  			assert.Equal(t, orch1.OrchestrationID, orl.Data[i].OrchestrationID)
   191  			assert.Equal(t, operations[i].OperationID, orl.Data[i].OperationID)
   192  		}
   193  	})
   194  }
   195  
   196  func TestClient_ListOperations(t *testing.T) {
   197  	t.Run("test_URL_params_pagination__NoError_path", func(t *testing.T) {
   198  		// given
   199  		called := 0
   200  		params := ListParameters{
   201  			PageSize: 2,
   202  			States:   []string{"failed", "in progress", "pending"},
   203  		}
   204  		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   205  			called++
   206  			assert.Equal(t, http.MethodGet, r.Method)
   207  			assert.Equal(t, fmt.Sprintf("/orchestrations/%s/operations", orch1.OrchestrationID), r.URL.Path)
   208  			assert.Equal(t, fmt.Sprintf("Bearer %s", fixToken), r.Header.Get("Authorization"))
   209  			query := r.URL.Query()
   210  			var err error
   211  			assert.ElementsMatch(t, []string{strconv.Itoa(called)}, query[pagination.PageParam])
   212  			assert.ElementsMatch(t, []string{strconv.Itoa(params.PageSize)}, query[pagination.PageSizeParam])
   213  			if called == 2 {
   214  				assert.ElementsMatch(t, []string{"in progress", "pending"}, query[StateParam])
   215  			} else {
   216  				assert.ElementsMatch(t, params.States, query[StateParam])
   217  			}
   218  
   219  			err = respondOperationListWithFailed(w, operationsWithFailedState[(called-1)*params.PageSize:called*params.PageSize], query[StateParam], 4)
   220  			require.NoError(t, err)
   221  		}))
   222  		defer ts.Close()
   223  		client := NewClient(context.TODO(), ts.URL, fixToken)
   224  		// when
   225  		orl, err := client.ListOperations(orch1.OrchestrationID, params)
   226  		// then
   227  		require.NoError(t, err)
   228  
   229  		assert.Equal(t, 2, called)
   230  		assert.Equal(t, 4, orl.Count)
   231  		assert.Equal(t, 4, orl.TotalCount)
   232  		assert.Len(t, orl.Data, 4)
   233  		var opS, orlD []string
   234  		for i := 0; i < 4; i++ {
   235  			opS = append(opS, operationsWithFailedState[i].OperationID)
   236  			orlD = append(orlD, orl.Data[i].OperationID)
   237  		}
   238  		sort.Strings(opS)
   239  		sort.Strings(orlD)
   240  		for i := 0; i < 4; i++ {
   241  			assert.Equal(t, orch1.OrchestrationID, orl.Data[i].OrchestrationID)
   242  			assert.Equal(t, opS, orlD)
   243  		}
   244  	})
   245  }
   246  
   247  func TestClient_GetOperation(t *testing.T) {
   248  	t.Run("test_URL__NoError_path", func(t *testing.T) {
   249  		// given
   250  		called := 0
   251  		oper := fixOperationDetailResponse("operation1", orch1.OrchestrationID)
   252  		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   253  			called++
   254  			assert.Equal(t, http.MethodGet, r.Method)
   255  			assert.Equal(t, fmt.Sprintf("/orchestrations/%s/operations/%s", orch1.OrchestrationID, oper.OperationID), r.URL.Path)
   256  			assert.Equal(t, fmt.Sprintf("Bearer %s", fixToken), r.Header.Get("Authorization"))
   257  
   258  			err := respondOperationDetail(w, oper)
   259  			require.NoError(t, err)
   260  		}))
   261  		defer ts.Close()
   262  		client := NewClient(context.TODO(), ts.URL, fixToken)
   263  
   264  		// when
   265  		od, err := client.GetOperation(orch1.OrchestrationID, oper.OperationID)
   266  
   267  		// then
   268  		require.NoError(t, err)
   269  		assert.Equal(t, 1, called)
   270  		assert.Equal(t, orch1.OrchestrationID, od.OrchestrationID)
   271  		assert.Equal(t, oper.OperationID, od.OperationID)
   272  	})
   273  }
   274  
   275  func TestClient_UpgradeKyma(t *testing.T) {
   276  	t.Run("test_URL_request_body_NoError_path", func(t *testing.T) {
   277  		// given
   278  		called := 0
   279  		params := Parameters{
   280  			Targets: TargetSpec{
   281  				Include: []RuntimeTarget{
   282  					{
   283  						Target: TargetAll,
   284  					},
   285  				},
   286  				Exclude: []RuntimeTarget{
   287  					{
   288  						GlobalAccount: "GA",
   289  					},
   290  				},
   291  			},
   292  			Strategy: StrategySpec{
   293  				Type:              ParallelStrategy,
   294  				Schedule:          time.Now().Format(time.RFC3339),
   295  				MaintenanceWindow: true,
   296  				Parallel: ParallelStrategySpec{
   297  					Workers: 2,
   298  				},
   299  			},
   300  		}
   301  		orchestrationID := orch1.OrchestrationID
   302  		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   303  			called++
   304  			assert.Equal(t, http.MethodPost, r.Method)
   305  			assert.Equal(t, "/upgrade/kyma", r.URL.Path)
   306  			assert.Equal(t, fmt.Sprintf("Bearer %s", fixToken), r.Header.Get("Authorization"))
   307  			reqBody := Parameters{}
   308  			err := json.NewDecoder(r.Body).Decode(&reqBody)
   309  			require.NoError(t, err)
   310  			assert.True(t, reflect.DeepEqual(params, reqBody))
   311  
   312  			err = respondUpgrade(w, orchestrationID)
   313  			require.NoError(t, err)
   314  		}))
   315  		defer ts.Close()
   316  		client := NewClient(context.TODO(), ts.URL, fixToken)
   317  
   318  		// when
   319  		ur, err := client.UpgradeKyma(params)
   320  
   321  		// then
   322  		require.NoError(t, err)
   323  		assert.Equal(t, 1, called)
   324  		assert.Equal(t, orchestrationID, ur.OrchestrationID)
   325  	})
   326  }
   327  
   328  func TestClient_CancelOrchestration(t *testing.T) {
   329  	t.Run("test_URL__NoError_path", func(t *testing.T) {
   330  		// given
   331  		called := 0
   332  		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   333  			called++
   334  			assert.Equal(t, http.MethodPut, r.Method)
   335  			assert.Equal(t, fmt.Sprintf("/orchestrations/%s/cancel", orch1.OrchestrationID), r.URL.Path)
   336  			assert.Equal(t, fmt.Sprintf("Bearer %s", fixToken), r.Header.Get("Authorization"))
   337  
   338  			err := respondStatus(w, orch1)
   339  			require.NoError(t, err)
   340  		}))
   341  		defer ts.Close()
   342  		client := NewClient(context.TODO(), ts.URL, fixToken)
   343  
   344  		// when
   345  		err := client.CancelOrchestration(orch1.OrchestrationID)
   346  
   347  		// then
   348  		require.NoError(t, err)
   349  		assert.Equal(t, 1, called)
   350  	})
   351  }
   352  
   353  func TestClient_RetryOrchestration(t *testing.T) {
   354  	t.Run("test_URL_NoError_path", func(t *testing.T) {
   355  		// given
   356  		called := 0
   357  		operationIDs := []string{"operation_id_0", "operation_id_1"}
   358  		ids := []string{}
   359  		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   360  			called++
   361  			assert.Equal(t, http.MethodPost, r.Method)
   362  			assert.Equal(t, fmt.Sprintf("/orchestrations/%s/retry", orch1.OrchestrationID), r.URL.Path)
   363  			assert.Equal(t, fmt.Sprintf("Bearer %s", fixToken), r.Header.Get("Authorization"))
   364  			assert.Equal(t, "application/x-www-form-urlencoded", r.Header.Get("Content-Type"))
   365  
   366  			for _, id := range operationIDs {
   367  				ids = append(ids, "operation-id="+id)
   368  			}
   369  
   370  			buf := new(bytes.Buffer)
   371  			buf.ReadFrom(r.Body)
   372  			body := buf.String()
   373  			assert.Equal(t, strings.Join(operationIDs, "&"), body)
   374  
   375  			err := respondRetry(w, orch1.OrchestrationID, operationIDs)
   376  			require.NoError(t, err)
   377  
   378  		}))
   379  		defer ts.Close()
   380  		client := NewClient(context.TODO(), ts.URL, fixToken)
   381  		expectedRr := RetryResponse{
   382  			OrchestrationID: orch1.OrchestrationID,
   383  			RetryShoots:     []string{"Shoot-instance-A", "Shoot-instance-B"},
   384  			Msg:             "retry operations are queued for processing",
   385  		}
   386  
   387  		// when
   388  		rr, err := client.RetryOrchestration(orch1.OrchestrationID, operationIDs, false)
   389  
   390  		// then
   391  		require.NoError(t, err)
   392  		assert.Equal(t, 1, called)
   393  		assert.Equal(t, expectedRr, rr)
   394  
   395  		// when
   396  		operationIDs = nil
   397  
   398  		expectedRr.RetryShoots = []string{"Shoot-instance-C", "Shoot-instance-D"}
   399  		rr, err = client.RetryOrchestration(orch1.OrchestrationID, operationIDs, false)
   400  
   401  		// then
   402  		require.NoError(t, err)
   403  		assert.Equal(t, 2, called)
   404  		assert.Equal(t, expectedRr, rr)
   405  	})
   406  }
   407  
   408  func fixStatusResponse(id string) StatusResponse {
   409  	return StatusResponse{
   410  		OrchestrationID: id,
   411  		State:           "succeeded",
   412  		Description:     id,
   413  		CreatedAt:       time.Now(),
   414  		UpdatedAt:       time.Now(),
   415  		Parameters:      Parameters{},
   416  		OperationStats: map[string]int{
   417  			"succeeded": 5,
   418  		},
   419  	}
   420  }
   421  
   422  func fixOperationResponse(id, orchestrationID, state string) OperationResponse {
   423  	return OperationResponse{
   424  		OperationID:     id,
   425  		RuntimeID:       id,
   426  		OrchestrationID: orchestrationID,
   427  		State:           state,
   428  	}
   429  }
   430  
   431  func fixOperationDetailResponse(id, orchestrationID string) OperationDetailResponse {
   432  	return OperationDetailResponse{
   433  		OperationResponse: fixOperationResponse(id, orchestrationID, ""),
   434  	}
   435  }
   436  
   437  func respondStatusList(w http.ResponseWriter, statuses []StatusResponse, totalCount int) error {
   438  	srl := StatusResponseList{
   439  		Data:       statuses,
   440  		Count:      len(statuses),
   441  		TotalCount: totalCount,
   442  	}
   443  	data, err := json.Marshal(srl)
   444  	if err != nil {
   445  		w.WriteHeader(http.StatusInternalServerError)
   446  		return err
   447  	}
   448  
   449  	w.Header().Set("Content-Type", "application/json")
   450  	w.WriteHeader(http.StatusOK)
   451  	_, err = w.Write(data)
   452  	return err
   453  }
   454  
   455  func respondStatus(w http.ResponseWriter, status StatusResponse) error {
   456  	data, err := json.Marshal(status)
   457  	if err != nil {
   458  		w.WriteHeader(http.StatusInternalServerError)
   459  		return err
   460  	}
   461  
   462  	w.Header().Set("Content-Type", "application/json")
   463  	w.WriteHeader(http.StatusOK)
   464  	_, err = w.Write(data)
   465  	return err
   466  }
   467  
   468  func respondOperationList(w http.ResponseWriter, operations []OperationResponse, totalCount int) error {
   469  	orl := OperationResponseList{
   470  		Data:       operations,
   471  		Count:      len(operations),
   472  		TotalCount: totalCount,
   473  	}
   474  	data, err := json.Marshal(orl)
   475  	if err != nil {
   476  		w.WriteHeader(http.StatusInternalServerError)
   477  		return err
   478  	}
   479  
   480  	w.Header().Set("Content-Type", "application/json")
   481  	w.WriteHeader(http.StatusOK)
   482  	_, err = w.Write(data)
   483  	return err
   484  }
   485  
   486  func respondOperationListWithFailed(w http.ResponseWriter, operations []OperationResponse, queryState []string, totalCount int) error {
   487  	var operationR []OperationResponse
   488  	if slices.Contains(queryState, "failed") {
   489  		for i := 0; i < totalCount; i++ {
   490  			if operationsWithFailedState[i].State == "failed" && i >= len(operations) {
   491  				operationR = append(operationR, operationsWithFailedState[i])
   492  			}
   493  		}
   494  	}
   495  	for i, _ := range operations {
   496  		if operations[i].State != "failed" {
   497  			operationR = append(operationR, operations[i])
   498  		}
   499  	}
   500  	orl := OperationResponseList{
   501  		Data:       operationR,
   502  		Count:      len(operationR),
   503  		TotalCount: totalCount,
   504  	}
   505  	data, err := json.Marshal(orl)
   506  	if err != nil {
   507  		w.WriteHeader(http.StatusInternalServerError)
   508  		return err
   509  	}
   510  
   511  	w.Header().Set("Content-Type", "application/json")
   512  	w.WriteHeader(http.StatusOK)
   513  	_, err = w.Write(data)
   514  	return err
   515  }
   516  
   517  func respondOperationDetail(w http.ResponseWriter, operation OperationDetailResponse) error {
   518  	data, err := json.Marshal(operation)
   519  	if err != nil {
   520  		w.WriteHeader(http.StatusInternalServerError)
   521  		return err
   522  	}
   523  
   524  	w.Header().Set("Content-Type", "application/json")
   525  	w.WriteHeader(http.StatusOK)
   526  	_, err = w.Write(data)
   527  	return err
   528  }
   529  
   530  func respondUpgrade(w http.ResponseWriter, orchestrationID string) error {
   531  	ur := UpgradeResponse{
   532  		OrchestrationID: orchestrationID,
   533  	}
   534  	data, err := json.Marshal(ur)
   535  	if err != nil {
   536  		w.WriteHeader(http.StatusInternalServerError)
   537  		return err
   538  	}
   539  
   540  	w.Header().Set("Content-Type", "application/json")
   541  	w.WriteHeader(http.StatusAccepted)
   542  	_, err = w.Write(data)
   543  	return err
   544  }
   545  
   546  func respondRetry(w http.ResponseWriter, orchestrationID string, operationIDs []string) error {
   547  	rr := RetryResponse{
   548  		OrchestrationID: orchestrationID,
   549  	}
   550  
   551  	if len(operationIDs) == 0 {
   552  		rr.RetryShoots = []string{"Shoot-instance-C", "Shoot-instance-D"}
   553  		rr.Msg = "retry operations are queued for processing"
   554  	} else {
   555  		rr.RetryShoots = []string{"Shoot-instance-A", "Shoot-instance-B"}
   556  		rr.Msg = "retry operations are queued for processing"
   557  	}
   558  
   559  	data, err := json.Marshal(rr)
   560  	if err != nil {
   561  		w.WriteHeader(http.StatusInternalServerError)
   562  		return err
   563  	}
   564  
   565  	w.Header().Set("Content-Type", "application/json")
   566  	w.WriteHeader(http.StatusAccepted)
   567  	_, err = w.Write(data)
   568  	return err
   569  }