github.com/shyftnetwork/go-empyrean@v1.8.3-0.20191127201940-fbfca9338f04/cmd/swarm/swarm-smoke/feed_upload_and_sync.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/md5"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"net/http/httptrace"
    12  	"os"
    13  	"os/exec"
    14  	"strings"
    15  	"sync"
    16  	"time"
    17  
    18  	"github.com/ShyftNetwork/go-empyrean/common/hexutil"
    19  	"github.com/ShyftNetwork/go-empyrean/crypto"
    20  	"github.com/ShyftNetwork/go-empyrean/log"
    21  	"github.com/ShyftNetwork/go-empyrean/swarm/storage/feed"
    22  
    23  	"github.com/ShyftNetwork/go-empyrean/metrics"
    24  	"github.com/ShyftNetwork/go-empyrean/swarm/api/client"
    25  	"github.com/ShyftNetwork/go-empyrean/swarm/spancontext"
    26  	"github.com/ShyftNetwork/go-empyrean/swarm/testutil"
    27  	colorable "github.com/mattn/go-colorable"
    28  	opentracing "github.com/opentracing/opentracing-go"
    29  	"github.com/pborman/uuid"
    30  	cli "gopkg.in/urfave/cli.v1"
    31  )
    32  
    33  const (
    34  	feedRandomDataLength = 8
    35  )
    36  
    37  func cliFeedUploadAndSync(c *cli.Context) error {
    38  	metrics.GetOrRegisterCounter("feed-and-sync", nil).Inc(1)
    39  	log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.Lvl(verbosity), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))))
    40  
    41  	errc := make(chan error)
    42  	go func() {
    43  		errc <- feedUploadAndSync(c)
    44  	}()
    45  
    46  	select {
    47  	case err := <-errc:
    48  		if err != nil {
    49  			metrics.GetOrRegisterCounter("feed-and-sync.fail", nil).Inc(1)
    50  		}
    51  		return err
    52  	case <-time.After(time.Duration(timeout) * time.Second):
    53  		metrics.GetOrRegisterCounter("feed-and-sync.timeout", nil).Inc(1)
    54  		return fmt.Errorf("timeout after %v sec", timeout)
    55  	}
    56  }
    57  
    58  // TODO: retrieve with manifest + extract repeating code
    59  func feedUploadAndSync(c *cli.Context) error {
    60  	defer func(now time.Time) { log.Info("total time", "time", time.Since(now), "size (kb)", filesize) }(time.Now())
    61  
    62  	generateEndpoints(scheme, cluster, appName, from, to)
    63  
    64  	log.Info("generating and uploading feeds to " + endpoints[0] + " and syncing")
    65  
    66  	// create a random private key to sign updates with and derive the address
    67  	pkFile, err := ioutil.TempFile("", "swarm-feed-smoke-test")
    68  	if err != nil {
    69  		return err
    70  	}
    71  	defer pkFile.Close()
    72  	defer os.Remove(pkFile.Name())
    73  
    74  	privkeyHex := "0000000000000000000000000000000000000000000000000000000000001976"
    75  	privKey, err := crypto.HexToECDSA(privkeyHex)
    76  	if err != nil {
    77  		return err
    78  	}
    79  	user := crypto.PubkeyToAddress(privKey.PublicKey)
    80  	userHex := hexutil.Encode(user.Bytes())
    81  
    82  	// save the private key to a file
    83  	_, err = io.WriteString(pkFile, privkeyHex)
    84  	if err != nil {
    85  		return err
    86  	}
    87  
    88  	// keep hex strings for topic and subtopic
    89  	var topicHex string
    90  	var subTopicHex string
    91  
    92  	// and create combination hex topics for bzz-feed retrieval
    93  	// xor'ed with topic (zero-value topic if no topic)
    94  	var subTopicOnlyHex string
    95  	var mergedSubTopicHex string
    96  
    97  	// generate random topic and subtopic and put a hex on them
    98  	topicBytes, err := generateRandomData(feed.TopicLength)
    99  	topicHex = hexutil.Encode(topicBytes)
   100  	subTopicBytes, err := generateRandomData(8)
   101  	subTopicHex = hexutil.Encode(subTopicBytes)
   102  	if err != nil {
   103  		return err
   104  	}
   105  	mergedSubTopic, err := feed.NewTopic(subTopicHex, topicBytes)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	mergedSubTopicHex = hexutil.Encode(mergedSubTopic[:])
   110  	subTopicOnlyBytes, err := feed.NewTopic(subTopicHex, nil)
   111  	if err != nil {
   112  		return err
   113  	}
   114  	subTopicOnlyHex = hexutil.Encode(subTopicOnlyBytes[:])
   115  
   116  	// create feed manifest, topic only
   117  	var out bytes.Buffer
   118  	cmd := exec.Command("swarm", "--bzzapi", endpoints[0], "feed", "create", "--topic", topicHex, "--user", userHex)
   119  	cmd.Stdout = &out
   120  	log.Debug("create feed manifest topic cmd", "cmd", cmd)
   121  	err = cmd.Run()
   122  	if err != nil {
   123  		return err
   124  	}
   125  	manifestWithTopic := strings.TrimRight(out.String(), string([]byte{0x0a}))
   126  	if len(manifestWithTopic) != 64 {
   127  		return fmt.Errorf("unknown feed create manifest hash format (topic): (%d) %s", len(out.String()), manifestWithTopic)
   128  	}
   129  	log.Debug("create topic feed", "manifest", manifestWithTopic)
   130  	out.Reset()
   131  
   132  	// create feed manifest, subtopic only
   133  	cmd = exec.Command("swarm", "--bzzapi", endpoints[0], "feed", "create", "--name", subTopicHex, "--user", userHex)
   134  	cmd.Stdout = &out
   135  	log.Debug("create feed manifest subtopic cmd", "cmd", cmd)
   136  	err = cmd.Run()
   137  	if err != nil {
   138  		return err
   139  	}
   140  	manifestWithSubTopic := strings.TrimRight(out.String(), string([]byte{0x0a}))
   141  	if len(manifestWithSubTopic) != 64 {
   142  		return fmt.Errorf("unknown feed create manifest hash format (subtopic): (%d) %s", len(out.String()), manifestWithSubTopic)
   143  	}
   144  	log.Debug("create subtopic feed", "manifest", manifestWithTopic)
   145  	out.Reset()
   146  
   147  	// create feed manifest, merged topic
   148  	cmd = exec.Command("swarm", "--bzzapi", endpoints[0], "feed", "create", "--topic", topicHex, "--name", subTopicHex, "--user", userHex)
   149  	cmd.Stdout = &out
   150  	log.Debug("create feed manifest mergetopic cmd", "cmd", cmd)
   151  	err = cmd.Run()
   152  	if err != nil {
   153  		log.Error(err.Error())
   154  		return err
   155  	}
   156  	manifestWithMergedTopic := strings.TrimRight(out.String(), string([]byte{0x0a}))
   157  	if len(manifestWithMergedTopic) != 64 {
   158  		return fmt.Errorf("unknown feed create manifest hash format (mergedtopic): (%d) %s", len(out.String()), manifestWithMergedTopic)
   159  	}
   160  	log.Debug("create mergedtopic feed", "manifest", manifestWithMergedTopic)
   161  	out.Reset()
   162  
   163  	// create test data
   164  	data, err := generateRandomData(feedRandomDataLength)
   165  	if err != nil {
   166  		return err
   167  	}
   168  	h := md5.New()
   169  	h.Write(data)
   170  	dataHash := h.Sum(nil)
   171  	dataHex := hexutil.Encode(data)
   172  
   173  	// update with topic
   174  	cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", endpoints[0], "feed", "update", "--topic", topicHex, dataHex)
   175  	cmd.Stdout = &out
   176  	log.Debug("update feed manifest topic cmd", "cmd", cmd)
   177  	err = cmd.Run()
   178  	if err != nil {
   179  		return err
   180  	}
   181  	log.Debug("feed update topic", "out", out)
   182  	out.Reset()
   183  
   184  	// update with subtopic
   185  	cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", endpoints[0], "feed", "update", "--name", subTopicHex, dataHex)
   186  	cmd.Stdout = &out
   187  	log.Debug("update feed manifest subtopic cmd", "cmd", cmd)
   188  	err = cmd.Run()
   189  	if err != nil {
   190  		return err
   191  	}
   192  	log.Debug("feed update subtopic", "out", out)
   193  	out.Reset()
   194  
   195  	// update with merged topic
   196  	cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", endpoints[0], "feed", "update", "--topic", topicHex, "--name", subTopicHex, dataHex)
   197  	cmd.Stdout = &out
   198  	log.Debug("update feed manifest merged topic cmd", "cmd", cmd)
   199  	err = cmd.Run()
   200  	if err != nil {
   201  		return err
   202  	}
   203  	log.Debug("feed update mergedtopic", "out", out)
   204  	out.Reset()
   205  
   206  	time.Sleep(3 * time.Second)
   207  
   208  	// retrieve the data
   209  	wg := sync.WaitGroup{}
   210  	for _, endpoint := range endpoints {
   211  		// raw retrieve, topic only
   212  		for _, hex := range []string{topicHex, subTopicOnlyHex, mergedSubTopicHex} {
   213  			wg.Add(1)
   214  			ruid := uuid.New()[:8]
   215  			go func(hex string, endpoint string, ruid string) {
   216  				for {
   217  					err := fetchFeed(hex, userHex, endpoint, dataHash, ruid)
   218  					if err != nil {
   219  						continue
   220  					}
   221  
   222  					wg.Done()
   223  					return
   224  				}
   225  			}(hex, endpoint, ruid)
   226  
   227  		}
   228  	}
   229  	wg.Wait()
   230  	log.Info("all endpoints synced random data successfully")
   231  
   232  	// upload test file
   233  	seed := int(time.Now().UnixNano() / 1e6)
   234  	log.Info("feed uploading to "+endpoints[0]+" and syncing", "seed", seed)
   235  
   236  	randomBytes := testutil.RandomBytes(seed, filesize*1000)
   237  
   238  	hash, err := upload(&randomBytes, endpoints[0])
   239  	if err != nil {
   240  		return err
   241  	}
   242  	hashBytes, err := hexutil.Decode("0x" + hash)
   243  	if err != nil {
   244  		return err
   245  	}
   246  	multihashHex := hexutil.Encode(hashBytes)
   247  	fileHash, err := digest(bytes.NewReader(randomBytes))
   248  	if err != nil {
   249  		return err
   250  	}
   251  
   252  	log.Info("uploaded successfully", "hash", hash, "digest", fmt.Sprintf("%x", fileHash))
   253  
   254  	// update file with topic
   255  	cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", endpoints[0], "feed", "update", "--topic", topicHex, multihashHex)
   256  	cmd.Stdout = &out
   257  	err = cmd.Run()
   258  	if err != nil {
   259  		return err
   260  	}
   261  	log.Debug("feed update topic", "out", out)
   262  	out.Reset()
   263  
   264  	// update file with subtopic
   265  	cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", endpoints[0], "feed", "update", "--name", subTopicHex, multihashHex)
   266  	cmd.Stdout = &out
   267  	err = cmd.Run()
   268  	if err != nil {
   269  		return err
   270  	}
   271  	log.Debug("feed update subtopic", "out", out)
   272  	out.Reset()
   273  
   274  	// update file with merged topic
   275  	cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", endpoints[0], "feed", "update", "--topic", topicHex, "--name", subTopicHex, multihashHex)
   276  	cmd.Stdout = &out
   277  	err = cmd.Run()
   278  	if err != nil {
   279  		return err
   280  	}
   281  	log.Debug("feed update mergedtopic", "out", out)
   282  	out.Reset()
   283  
   284  	time.Sleep(3 * time.Second)
   285  
   286  	for _, endpoint := range endpoints {
   287  
   288  		// manifest retrieve, topic only
   289  		for _, url := range []string{manifestWithTopic, manifestWithSubTopic, manifestWithMergedTopic} {
   290  			wg.Add(1)
   291  			ruid := uuid.New()[:8]
   292  			go func(url string, endpoint string, ruid string) {
   293  				for {
   294  					err := fetch(url, endpoint, fileHash, ruid)
   295  					if err != nil {
   296  						continue
   297  					}
   298  
   299  					wg.Done()
   300  					return
   301  				}
   302  			}(url, endpoint, ruid)
   303  		}
   304  
   305  	}
   306  	wg.Wait()
   307  	log.Info("all endpoints synced random file successfully")
   308  
   309  	return nil
   310  }
   311  
   312  func fetchFeed(topic string, user string, endpoint string, original []byte, ruid string) error {
   313  	ctx, sp := spancontext.StartSpan(context.Background(), "feed-and-sync.fetch")
   314  	defer sp.Finish()
   315  
   316  	log.Trace("sleeping", "ruid", ruid)
   317  	time.Sleep(3 * time.Second)
   318  
   319  	log.Trace("http get request (feed)", "ruid", ruid, "api", endpoint, "topic", topic, "user", user)
   320  
   321  	var tn time.Time
   322  	reqUri := endpoint + "/bzz-feed:/?topic=" + topic + "&user=" + user
   323  	req, _ := http.NewRequest("GET", reqUri, nil)
   324  
   325  	opentracing.GlobalTracer().Inject(
   326  		sp.Context(),
   327  		opentracing.HTTPHeaders,
   328  		opentracing.HTTPHeadersCarrier(req.Header))
   329  
   330  	trace := client.GetClientTrace("feed-and-sync - http get", "feed-and-sync", ruid, &tn)
   331  
   332  	req = req.WithContext(httptrace.WithClientTrace(ctx, trace))
   333  	transport := http.DefaultTransport
   334  
   335  	//transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
   336  
   337  	tn = time.Now()
   338  	res, err := transport.RoundTrip(req)
   339  	if err != nil {
   340  		log.Error(err.Error(), "ruid", ruid)
   341  		return err
   342  	}
   343  
   344  	log.Trace("http get response (feed)", "ruid", ruid, "api", endpoint, "topic", topic, "user", user, "code", res.StatusCode, "len", res.ContentLength)
   345  
   346  	if res.StatusCode != 200 {
   347  		return fmt.Errorf("expected status code %d, got %v (ruid %v)", 200, res.StatusCode, ruid)
   348  	}
   349  
   350  	defer res.Body.Close()
   351  
   352  	rdigest, err := digest(res.Body)
   353  	if err != nil {
   354  		log.Warn(err.Error(), "ruid", ruid)
   355  		return err
   356  	}
   357  
   358  	if !bytes.Equal(rdigest, original) {
   359  		err := fmt.Errorf("downloaded imported file md5=%x is not the same as the generated one=%x", rdigest, original)
   360  		log.Warn(err.Error(), "ruid", ruid)
   361  		return err
   362  	}
   363  
   364  	log.Trace("downloaded file matches random file", "ruid", ruid, "len", res.ContentLength)
   365  
   366  	return nil
   367  }