コード例 #1
0
ファイル: main.go プロジェクト: cdshann/minimega
func main() {
	flag.Parse()

	logSetup()

	log.Debug("using minimega: %v", *f_minimega)

	// invoke minimega and get the doc json
	doc, err := exec.Command(*f_minimega, "-cli").Output()
	if err != nil {
		log.Fatalln(err)
	}
	log.Debug("got doc: %v", string(doc))

	// decode the JSON for our template
	if err := json.Unmarshal(doc, &handlers); err != nil {
		log.Fatalln(err)
	}

	exclude = strings.Split(*f_exclude, ",")
	values = strings.Split(*f_values, ",")

	for {
		if err := fuzz(); err != nil {
			log.Fatal("fuzz: %v", err)
		}
		if err := cleanup(); err != nil {
			log.Fatal("cleanup: %v", err)
		}
	}
}
コード例 #2
0
ファイル: main.go プロジェクト: cdshann/minimega
func main() {
	flag.Parse()

	logSetup()

	// register CLI handlers
	for i := range cliHandlers {
		err := minicli.Register(&cliHandlers[i])
		if err != nil {
			log.Fatal("invalid handler, `%v` -- %v", cliHandlers[i].HelpShort, err)
		}
	}

	var err error
	hostname, err = os.Hostname()
	if err != nil {
		log.Fatal("unable to get hostname: %v", hostname)
	}

	rond, err = ron.NewServer(*f_port, *f_path)
	if err != nil {
		log.Fatal("unable to create server: %v", err)
	}

	for {
		line, err := goreadline.Readline("rond$ ", true)
		if err != nil {
			return
		}
		command := string(line)
		log.Debug("got from stdin: `%s`", line)

		cmd, err := minicli.Compile(command)
		if err != nil {
			log.Error("%v", err)
			continue
		}

		// No command was returned, must have been a blank line or a comment
		// line. Either way, don't try to run a nil command.
		if cmd == nil {
			continue
		}

		for resp := range minicli.ProcessCommand(cmd) {
			minipager.DefaultPager.Page(resp.String())

			errs := resp.Error()
			if errs != "" {
				fmt.Fprintln(os.Stderr, errs)
			}
		}
	}
}
コード例 #3
0
ファイル: main.go プロジェクト: npe9/minimega
func runTests() {
	mm, err := miniclient.Dial(*f_base)
	if err != nil {
		log.Fatal("%v", err)
	}

	if *f_preamble != "" {
		out, err := runCommands(mm, *f_preamble)
		if err != nil {
			log.Fatal("%v", err)
		}

		log.Info(out)
	}

	// TODO: Should we quit minimega and restart it between each test?
	//quit := mustCompile(t, "quit 2")

	files, err := ioutil.ReadDir(*f_testDir)
	if err != nil {
		log.Fatal("%v", err)
	}

	for _, info := range files {
		if strings.HasSuffix(info.Name(), ".want") || strings.HasSuffix(info.Name(), ".got") {
			continue
		}

		log.Info("Running commands from %s", info.Name())
		fpath := path.Join(*f_testDir, info.Name())

		got, err := runCommands(mm, fpath)
		if err != nil {
			log.Fatal("%v", err)
		}

		// Record the output for offline comparison
		if err := ioutil.WriteFile(fpath+".got", []byte(got), os.FileMode(0644)); err != nil {
			log.Error("unable to write `%s` -- %v", fpath+".got", err)
		}

		want, err := ioutil.ReadFile(fpath + ".want")
		if err != nil {
			log.Error("unable to read file `%s` -- %v", fpath+".want", err)
			continue
		}

		if got != string(want) {
			log.Error("got != want for %s", info.Name())
		}

		//mm.runCommand(quit)
	}
}
コード例 #4
0
ファイル: main.go プロジェクト: cdshann/minimega
func main() {
	flag.Usage = usage
	flag.Parse()

	if *f_version {
		fmt.Println("miniccc", version.Revision, version.Date)
		fmt.Println(version.Copyright)
		os.Exit(0)
	}

	logSetup()

	if *f_tag {
		if runtime.GOOS == "windows" {
			log.Fatalln("tag updates are not available on windows miniccc clients")
		}

		if err := updateTag(); err != nil {
			log.Errorln(err)
		}
		return
	}

	// attempt to set up the base path
	if err := os.MkdirAll(*f_path, os.FileMode(0777)); err != nil {
		log.Fatal("mkdir base path: %v", err)
	}

	log.Debug("starting ron client with UUID: %v", Client.UUID)

	if err := dial(); err != nil {
		log.Fatal("unable to connect: %v", err)
	}

	go mux()
	go periodic()
	go commandHandler()
	heartbeat() // handshake is first heartbeat

	// create a listening domain socket for tag updates
	if runtime.GOOS != "windows" {
		go commandSocketStart()
	}

	// wait for SIGTERM
	sig := make(chan os.Signal, 1024)
	signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
	<-sig
}
コード例 #5
0
ファイル: vm_cli.go プロジェクト: jenareljam/minimega
func cliVmConfigField(c *minicli.Command, field string) *minicli.Response {
	resp := &minicli.Response{Host: hostname}

	// If there are no args it means that we want to display the current value
	nArgs := len(c.StringArgs) + len(c.ListArgs) + len(c.BoolArgs)

	var ok bool
	var fns VMConfigFns
	var config interface{}

	// Find the right config functions, baseConfigFns has highest priority
	if fns, ok = baseConfigFns[field]; ok {
		config = &vmConfig.BaseConfig
	} else if fns, ok = kvmConfigFns[field]; ok {
		config = &vmConfig.KVMConfig
	} else if fns, ok = containerConfigFns[field]; ok {
		config = &vmConfig.ContainerConfig
	} else {
		log.Fatal("unknown config field: `%s`", field)
	}

	if nArgs == 0 {
		resp.Response = fns.Print(config)
	} else {
		if err := fns.Update(config, c); err != nil {
			resp.Error = err.Error()
		}
	}

	return resp
}
コード例 #6
0
ファイル: vmconfig.go プロジェクト: jenareljam/minimega
func mustKVMConfig(val interface{}) *KVMConfig {
	if val, ok := val.(*KVMConfig); ok {
		return val
	}
	log.Fatal("`%#v` is not a KVMConfig", val)
	return nil
}
コード例 #7
0
ファイル: main.go プロジェクト: npe9/minimega
func main() {
	flag.Usage = usage
	flag.Parse()

	if *f_version {
		fmt.Println("miniccc", version.Revision, version.Date)
		fmt.Println(version.Copyright)
		os.Exit(0)
	}

	logSetup()

	// signal handling
	sig := make(chan os.Signal, 1024)
	signal.Notify(sig, os.Interrupt, syscall.SIGTERM)

	// start a ron client
	var err error
	c, err = ron.NewClient(*f_port, *f_parent, *f_serial, *f_path)
	if err != nil {
		log.Fatal("creating ron node: %v", err)
	}

	log.Debug("starting ron client with UUID: %v", c.UUID)

	go client()

	<-sig
	// terminate
}
コード例 #8
0
ファイル: minicli.go プロジェクト: cdshann/minimega
// Process a prepopulated Command
func ProcessCommand(c *Command) <-chan Responses {
	if !c.noOp && c.Call == nil {
		log.Fatal("command %v has no callback!", c)
	}

	respChan := make(chan Responses)

	go func() {
		if !c.noOp {
			c.Call(c, respChan)
		}

		// Append the command to the history
		if c.Record {
			history = append(history, c.Original)

			if len(history) > HistoryLen && HistoryLen > 0 {
				if firstHistoryTruncate {
					log.Warn("history length exceeds limit, truncating to %v entries", HistoryLen)
					firstHistoryTruncate = false
				}

				history = history[len(history)-HistoryLen:]
			}
		}

		close(respChan)
	}()

	return respChan
}
コード例 #9
0
ファイル: vmconfig.go プロジェクト: jenareljam/minimega
func mustContainerConfig(val interface{}) *ContainerConfig {
	if val, ok := val.(*ContainerConfig); ok {
		return val
	}
	log.Fatal("`%#v` is not a ContainerConfig", val)
	return nil
}
コード例 #10
0
ファイル: uuid_windows.go プロジェクト: cdshann/minimega
func unmangleUUID(uuid string) string {
	// string must be in the form:
	//	XXXXXXXX-XXXX-XXXX-YYYY-YYYYYYYYYYYY
	// the X characters are reversed at 2 byte intervals (big/little endian for a uuid?)
	var ret string
	re := regexp.MustCompile("[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}")

	u := re.FindString(strings.ToLower(uuid))
	if uuid == "" {
		log.Fatal("uuid failed to match uuid format: %v", uuid)
	}

	log.Debug("found uuid: %v", u)

	if getOSVer() != "Windows XP" {
		return u
	}

	ret += u[6:8]
	ret += u[4:6]
	ret += u[2:4]
	ret += u[:2]
	ret += "-"
	ret += u[11:13]
	ret += u[9:11]
	ret += "-"
	ret += u[16:18]
	ret += u[14:16]
	ret += u[18:]

	log.Debug("mangled/unmangled uuid: %v %v", u, ret)
	return ret
}
コード例 #11
0
ファイル: cli.go プロジェクト: jenareljam/minimega
// Wrapper for minicli.ProcessCommand for commands that use meshage.
// Specifically, for `mesh send all ...`, runs the subcommand locally and
// across meshage, combining the results from the two channels into a single
// channel. This is useful if you want to get the output of a command from all
// nodes in the cluster without having to run a command locally and over
// meshage.
func runCommandGlobally(cmd *minicli.Command) chan minicli.Responses {
	// Keep the original CLI input
	original := cmd.Original
	record := cmd.Record

	cmd, err := minicli.Compilef("mesh send %s .record %t %s", Wildcard, record, original)
	if err != nil {
		log.Fatal("cannot run `%v` globally -- %v", original, err)
	}
	cmd.Record = record

	cmdLock.Lock()

	var wg sync.WaitGroup

	out := make(chan minicli.Responses)

	cmd, err = cliPreprocessor(cmd)
	if err != nil {
		log.Errorln(err)
		out <- minicli.Responses{
			&minicli.Response{
				Host:  hostname,
				Error: err.Error(),
			},
		}
		close(out)
		return out
	}

	// Run the command (should be `mesh send all ...` and the subcommand which
	// should run locally).
	ins := []chan minicli.Responses{
		minicli.ProcessCommand(cmd),
		minicli.ProcessCommand(cmd.Subcommand),
	}

	// De-mux ins into out
	for _, in := range ins {
		wg.Add(1)
		go func(in chan minicli.Responses) {
			defer wg.Done()
			for v := range in {
				out <- v
			}
		}(in)
	}

	// Wait until everything has been read before closing the chan and
	// releasing the lock.
	go func() {
		defer cmdLock.Unlock()
		defer close(out)

		wg.Wait()
	}()

	return out
}
コード例 #12
0
ファイル: misc.go プロジェクト: ITLivLab/minimega
// registerHandlers registers all the provided handlers with minicli, panicking
// if any of the handlers fail to register.
func registerHandlers(name string, handlers []minicli.Handler) {
	for i := range handlers {
		err := minicli.Register(&handlers[i])
		if err != nil {
			log.Fatal("invalid handler, %s:%d -- %v", name, i, err)
		}
	}
}
コード例 #13
0
ファイル: minicli.go プロジェクト: cdshann/minimega
// MustCompile compiles the string, calling log.Fatal if the string is not a
// valid command. Should be used when providing a known command rather than
// processing user input.
func MustCompile(input string) *Command {
	c, err := Compile(input)
	if err != nil {
		log.Fatal("unable to compile `%s` -- %v", input, err)
	}

	return c
}
コード例 #14
0
ファイル: minitunnel.go プロジェクト: npe9/minimega
// register a transaction ID, adding a return channel to the mux
func (t *Tunnel) registerTID(TID int32) chan *tunnelMessage {
	if _, ok := t.tids[TID]; ok {
		log.Fatal("tid %v already exists!", TID)
	}
	c := make(chan *tunnelMessage, 1024)
	t.tids[TID] = c
	return c
}
コード例 #15
0
ファイル: minimega.go プロジェクト: cdshann/minimega
// Run a command through a JSON pipe, hand back channel for responses.
func (mm *Conn) Run(cmd *minicli.Command) chan *Response {
	if cmd == nil {
		// Language spec: "Receiving from a nil channel blocks forever."
		// Instead, make and immediately close the channel so that range
		// doesn't block and receives no values.
		out := make(chan *Response)
		close(out)

		return out
	}

	err := mm.enc.Encode(*cmd)
	if err != nil {
		log.Fatal("local command gob encode: %v", err)
	}
	log.Debugln("encoded command:", cmd)

	respChan := make(chan *Response)

	go func() {
		defer close(respChan)

		for {
			var r Response
			err = mm.dec.Decode(&r)
			if err != nil {
				if err == io.EOF {
					log.Fatal("server disconnected")
				}

				log.Fatal("local command gob decode: %v", err)
			}

			respChan <- &r
			if !r.More {
				log.Debugln("got last message")
				break
			} else {
				log.Debugln("expecting more data")
			}
		}
	}()

	return respChan
}
コード例 #16
0
ファイル: vm.go プロジェクト: ITLivLab/minimega
// mustFindMask returns the index of the specified mask in vmMasks. If the
// specified mask is not found, log.Fatal is called.
func mustFindMask(mask string) int {
	for i, v := range vmMasks {
		if v == mask {
			return i
		}
	}

	log.Fatal("missing `%s` in vmMasks", mask)
	return -1
}
コード例 #17
0
ファイル: uuid_windows.go プロジェクト: cdshann/minimega
func getUUID() string {
	out, err := exec.Command("wmic", "path", "win32_computersystemproduct", "get", "uuid").CombinedOutput()
	if err != nil {
		log.Fatal("wmic run: %v", err)
	}

	uuid := unmangleUUID(strings.TrimSpace(string(out)))
	log.Debug("got UUID: %v", uuid)

	return uuid
}
コード例 #18
0
ファイル: uuid_linux.go プロジェクト: cdshann/minimega
func getUUID() string {
	d, err := ioutil.ReadFile("/sys/devices/virtual/dmi/id/product_uuid")
	if err != nil {
		log.Fatal("unable to get UUID: %v", err)
	}

	uuid := strings.ToLower(strings.TrimSpace(string(d)))
	log.Debug("got UUID: %v", uuid)

	return uuid
}
コード例 #19
0
ファイル: minirouter.go プロジェクト: cdshann/minimega
func main() {
	// flags
	flag.Parse()

	logSetup()

	if *f_u != "" {
		log.Debug("updating with file: %v", *f_u)

		err := update(filepath.Join(*f_path, "minirouter"), *f_u)
		if err != nil {
			log.Errorln(err)
		}

		return
	}

	// check for a running instance of minirouter
	_, err := os.Stat(filepath.Join(*f_path, "minirouter"))
	if err == nil {
		if !*f_force {
			log.Fatalln("minirouter appears to already be running, override with -force")
		}
		log.Warn("minirouter may already be running, proceed with caution")
		err = os.Remove(filepath.Join(*f_path, "minirouter"))
		if err != nil {
			log.Fatalln(err)
		}
	}

	log.Debug("using path: %v", *f_path)

	// attempt to set up the base path
	err = os.MkdirAll(*f_path, os.FileMode(0770))
	if err != nil {
		log.Fatal("mkdir base path: %v", err)
	}

	// start the domain socket service
	go commandSocketStart()

	// signal handling
	sig := make(chan os.Signal, 1024)
	signal.Notify(sig, os.Interrupt, syscall.SIGTERM)

	<-sig

	// cleanup
	err = os.Remove(filepath.Join(*f_path, "minirouter"))
	if err != nil {
		log.Fatalln(err)
	}
}
コード例 #20
0
ファイル: dns.go プロジェクト: cdshann/minimega
func dnsServer() {
	log.Debugln("dnsServer")
	rand.Seed(time.Now().UnixNano())

	dns.HandleFunc(".", handleDnsRequest)
	server := &dns.Server{Addr: addr, Net: proto}

	err := server.ListenAndServe()
	if err != nil {
		log.Fatal(err.Error())
	}
}
コード例 #21
0
ファイル: node.go プロジェクト: summits/minimega
// broadcastListener listens for broadcast connection solicitations and connects to
// soliciting nodes.
func (n *Node) broadcastListener() {
	listenAddr := net.UDPAddr{
		IP:   net.IPv4(0, 0, 0, 0),
		Port: n.port,
	}
	ln, err := net.ListenUDP("udp4", &listenAddr)
	if err != nil {
		log.Fatal("broadcastListener: %v", err)
	}
	for {
		d := make([]byte, 1024)
		read, _, err := ln.ReadFromUDP(d)
		if err != nil {
			log.Error("broadcastListener ReadFromUDP: %v", err)
			continue
		}
		data := strings.Split(string(d[:read]), ":")
		if len(data) != 3 {
			log.Warn("got malformed udp data: %v", data)
			continue
		}
		if data[0] != "meshage" {
			log.Warn("got malformed udp data: %v", data)
			continue
		}
		namespace := data[1]
		host := data[2]
		if namespace != n.namespace {
			log.Debug("got solicitation from namespace %v, dropping", namespace)
			continue
		}
		if host == n.name {
			log.Debugln("got solicitation from myself, dropping")
			continue
		}
		log.Debug("got solicitation from %v", host)

		// to avoid spamming the node with connections, only 1/8 of the
		// nodes should try to connect. If there are < 16 nodes, then
		// always try.
		if len(n.clients) > SOLICIT_LIMIT {
			s := rand.NewSource(time.Now().UnixNano())
			r := rand.New(s)
			n := r.Intn(SOLICIT_RATIO)
			if n != 0 {
				log.Debugln("randomly skipping this solicitation")
				continue
			}
		}
		go n.dial(host, true)
	}
}
コード例 #22
0
ファイル: mux.go プロジェクト: cdshann/minimega
func (c *chans) add(ID int) chan *tunnelMessage {
	c.Lock()
	defer c.Unlock()

	if _, ok := c.chans[ID]; ok {
		log.Fatal("ID %v already exists!", ID)
	}

	ch := make(chan *tunnelMessage, 1024)
	c.chans[ID] = ch

	return ch
}
コード例 #23
0
ファイル: cli.go プロジェクト: cdshann/minimega
// runCommandGlobally runs the given command across all nodes on meshage,
// including the local node and combines the results into a single channel.
func runCommandGlobally(cmd *minicli.Command) <-chan minicli.Responses {
	// Keep the original CLI input
	original := cmd.Original
	record := cmd.Record

	cmd, err := minicli.Compilef("mesh send %s %s", Wildcard, original)
	if err != nil {
		log.Fatal("cannot run `%v` globally -- %v", original, err)
	}
	cmd.SetRecord(record)

	return runCommands(cmd, cmd.Subcommand)
}
コード例 #24
0
ファイル: bridge.go プロジェクト: jenareljam/minimega
// create the default bridge struct and create a goroutine to generate
// tap names for this host.
func init() {
	bridges = make(map[string]*Bridge)

	tapNameChan = make(chan string)

	go func() {
		for tapCount := 0; ; tapCount++ {
			tapName := fmt.Sprintf("mega_tap%v", tapCount)
			fpath := filepath.Join("/sys/class/net", tapName)

			if _, err := os.Stat(fpath); os.IsNotExist(err) {
				tapNameChan <- tapName
			} else if err != nil {
				log.Fatal("unable to stat file -- %v %v", fpath, err)
			}

			log.Debug("tapCount: %v", tapCount)
		}
	}()
}
コード例 #25
0
ファイル: optimize.go プロジェクト: postfix/minimega
func affinityUnselectCPU(vm *KvmVM) {
	// find and remove vm from its cpuset
	for k, v := range affinityCPUSets {
		for i, j := range v {
			if j.GetID() == vm.GetID() {
				if len(v) == 1 {
					affinityCPUSets[k] = []*KvmVM{}
				} else if i == 0 {
					affinityCPUSets[k] = v[1:]
				} else if i == len(v)-1 {
					affinityCPUSets[k] = v[:len(v)-1]
				} else {
					affinityCPUSets[k] = append(affinityCPUSets[k][:i], affinityCPUSets[k][i+1:]...)
				}
				return
			}
		}
	}
	log.Fatal("could not find vm %v in CPU set", vm.GetID())
}
コード例 #26
0
ファイル: minidoc.go プロジェクト: cdshann/minimega
func main() {
	flag.Parse()

	if !strings.HasSuffix(*f_minimega, "/") {
		*f_minimega += "/"
	}

	logSetup()

	err := initTemplates(*f_base)
	if err != nil {
		log.Fatal("failed to parse templates: %v", err)
	}

	if *f_exec {
		present.PlayEnabled = true
		http.Handle("/socket", NewSocketHandler())
	}

	log.Fatalln(http.ListenAndServe(*f_server, nil))
}
コード例 #27
0
ファイル: cli.go プロジェクト: ITLivLab/minimega
// Wrapper for minicli.ProcessCommand for commands that use meshage.
// Specifically, for `mesh send all ...`, runs the subcommand locally and
// across meshage, combining the results from the two channels into a single
// channel. This is useful if you want to get the output of a command from all
// nodes in the cluster without having to run a command locally and over
// meshage.
func runCommandGlobally(cmd *minicli.Command, record bool) chan minicli.Responses {
	cmdStr := fmt.Sprintf("mesh send %s %s", Wildcard, cmd.Original)
	cmd, err := minicli.CompileCommand(cmdStr)
	if err != nil {
		log.Fatal("cannot run `%v` globally -- %v", cmd.Original, err)
	}

	cmdLock.Lock()
	defer cmdLock.Unlock()

	var wg sync.WaitGroup

	out := make(chan minicli.Responses)

	// Run the command (should be `mesh send all ...` and the subcommand which
	// should run locally).
	ins := []chan minicli.Responses{
		minicli.ProcessCommand(cmd, record),
		minicli.ProcessCommand(cmd.Subcommand, record),
	}

	// De-mux ins into out
	for _, in := range ins {
		wg.Add(1)
		go func(in chan minicli.Responses) {
			defer wg.Done()
			for v := range in {
				out <- v
			}
		}(in)
	}

	// Wait until everything has been read before closing out
	go func() {
		wg.Wait()
		close(out)
	}()

	return out
}
コード例 #28
0
ファイル: minicli.go プロジェクト: npe9/minimega
// Process a prepopulated Command
func ProcessCommand(c *Command) chan Responses {
	if !c.noOp && c.Call == nil {
		log.Fatal("command %v has no callback!", c)
	}

	respChan := make(chan Responses)

	go func() {
		if !c.noOp {
			c.Call(c, respChan)
		}

		// Append the command to the history
		if c.Record {
			history = append(history, c.Original)
		}

		close(respChan)
	}()

	return respChan
}
コード例 #29
0
ファイル: bridges.go プロジェクト: cdshann/minimega
// NewBridges creates a new Bridges using d as the default bridge name and f as
// the format string for the tap names (e.g. "mega_tap%v").
func NewBridges(d, f string) *Bridges {
	nameChan := make(chan string)

	// Start a goroutine to generate tap names for us
	go func() {
		defer close(nameChan)

		for tapCount := 0; ; tapCount++ {
			tapName := fmt.Sprintf(f, tapCount)
			fpath := filepath.Join("/sys/class/net", tapName)

			if _, err := os.Stat(fpath); os.IsNotExist(err) {
				nameChan <- tapName
			} else if err != nil {
				log.Fatal("unable to stat file -- %v %v", fpath, err)
			}

			log.Debug("tapCount: %v", tapCount)
		}
	}()

	b := &Bridges{
		Default:  d,
		nameChan: nameChan,
		bridges:  map[string]*Bridge{},
	}

	// Start a goroutine to collect bandwidth stats every 5 seconds
	go func() {
		for {
			time.Sleep(5 * time.Second)

			b.updateBandwidthStats()
		}
	}()

	return b
}
コード例 #30
0
ファイル: main.go プロジェクト: cdshann/minimega
func dial() error {
	Client.Lock()
	defer Client.Unlock()

	var err error

	for i := Retries; i > 0; i-- {
		if *f_serial == "" {
			log.Debug("dial: %v:%v:%v", *f_family, *f_parent, *f_port)

			var addr string
			switch *f_family {
			case "tcp":
				addr = fmt.Sprintf("%v:%v", *f_parent, *f_port)
			case "unix":
				addr = *f_parent
			default:
				log.Fatal("invalid ron dial network family: %v", *f_family)
			}

			Client.conn, err = net.Dial(*f_family, addr)
		} else {
			Client.conn, err = dialSerial(*f_serial)
		}

		if err == nil {
			Client.enc = gob.NewEncoder(Client.conn)
			Client.dec = gob.NewDecoder(Client.conn)
			return nil
		}

		log.Error("%v, retries = %v", err, i)
		time.Sleep(15 * time.Second)
	}

	return err
}