<commit_before>package herd

import (
	"fmt"
	"github.com/Symantec/Dominator/lib/filesystem"
	"github.com/Symantec/Dominator/lib/filter"
	subproto "github.com/Symantec/Dominator/proto/sub"
	"os"
	"path"
	"syscall"
	"time"
)

type state struct {
	requiredInodeToSubInode map[uint64]uint64
	inodesChanged           map[uint64]bool // Required inode number.
	subFS                   *filesystem.FileSystem
	requiredFS              *filesystem.FileSystem
}

func (sub *Sub) buildUpdateRequest(request *subproto.UpdateRequest) {
	fmt.Println("buildUpdateRequest()") // TODO(rgooch): Delete debugging.
	var state state
	state.subFS = &sub.fileSystem.FileSystem
	requiredImage := sub.herd.getImage(sub.requiredImage)
	state.requiredFS = requiredImage.FileSystem
	filter := requiredImage.Filter
	request.Triggers = requiredImage.Triggers
	state.requiredInodeToSubInode = make(map[uint64]uint64)
	state.inodesChanged = make(map[uint64]bool)
	var rusageStart, rusageStop syscall.Rusage
	syscall.Getrusage(syscall.RUSAGE_SELF, &rusageStart)
	compareDirectories(request, &state,
		&state.subFS.DirectoryInode, &state.requiredFS.DirectoryInode,
		"/", filter)
	syscall.Getrusage(syscall.RUSAGE_SELF, &rusageStop) // HACK
	cpuTime := time.Duration(rusageStop.Utime.Sec)*time.Second +
		time.Duration(rusageStop.Utime.Usec)*time.Microsecond -
		time.Duration(rusageStart.Utime.Sec)*time.Second -
		time.Duration(rusageStart.Utime.Usec)*time.Microsecond
	fmt.Printf("Build update request took: %s user CPU time\n", cpuTime)
}

func compareDirectories(request *subproto.UpdateRequest, state *state,
	subDirectory, requiredDirectory *filesystem.DirectoryInode,
	myPathName string, filter *filter.Filter) {
	// First look for entries that should be deleted.
	if subDirectory != nil {
		for name := range subDirectory.EntriesByName {
			pathname := path.Join(myPathName, name)
			if filter.Match(pathname) {
				continue
			}
			if _, ok := requiredDirectory.EntriesByName[name]; !ok {
				request.PathsToDelete = append(request.PathsToDelete, pathname)
				fmt.Printf("Delete: %s\n", pathname) // HACK
			}
		}
	}
	for name, requiredEntry := range requiredDirectory.EntriesByName {
		pathname := path.Join(myPathName, name)
		if filter.Match(pathname) {
			continue
		}
		var subEntry *filesystem.DirectoryEntry
		if subDirectory != nil {
			if se, ok := subDirectory.EntriesByName[name]; ok {
				subEntry = se
			}
		}
		if subEntry == nil {
			addEntry(request, state, requiredEntry, pathname)
		} else {
			compareEntries(request, state, subEntry, requiredEntry, pathname,
				filter)
		}
		// If a directory: descend (possibly with the directory for the sub).
		requiredInode := requiredEntry.Inode()
		if requiredInode, ok := requiredInode.(*filesystem.DirectoryInode); ok {
			var subInode *filesystem.DirectoryInode
			if subEntry != nil {
				if si, ok := subEntry.Inode().(*filesystem.DirectoryInode); ok {
					subInode = si
				}
			}
			compareDirectories(request, state, subInode, requiredInode,
				pathname, filter)
		}
	}
}

func addEntry(request *subproto.UpdateRequest, state *state,
	requiredEntry *filesystem.DirectoryEntry, myPathName string) {
	requiredInode := requiredEntry.Inode()
	if requiredInode, ok := requiredInode.(*filesystem.DirectoryInode); ok {
		makeDirectory(request, requiredInode, myPathName, true)
	} else {
		addInode(request, state, requiredEntry, myPathName)
	}
}

func compareEntries(request *subproto.UpdateRequest, state *state,
	subEntry, requiredEntry *filesystem.DirectoryEntry,
	myPathName string, filter *filter.Filter) {
	var sameType, sameMetadata, sameData bool
	switch requiredInode := requiredEntry.Inode().(type) {
	case *filesystem.RegularInode:
		sameType, sameMetadata, sameData =
			compareRegularFile(request, state, subEntry, requiredInode,
				myPathName)
	case *filesystem.SymlinkInode:
		sameType, sameMetadata, sameData =
			compareSymlink(request, state, subEntry, requiredInode, myPathName)
	case *filesystem.Inode:
		sameType, sameMetadata, sameData =
			compareFile(request, state, subEntry, requiredInode, myPathName)
	case *filesystem.DirectoryInode:
		compareDirectory(request, state, subEntry, requiredInode, myPathName,
			filter)
		return
	default:
		panic("Unsupported entry type")
	}
	if sameType && sameData && sameMetadata {
		relink(request, state, subEntry, requiredEntry, myPathName)
		return
	}
	if sameType && sameData {
		updateMetadata(request, state, subEntry, requiredEntry, myPathName)
		relink(request, state, subEntry, requiredEntry, myPathName)
		return
	}
	request.PathsToDelete = append(request.PathsToDelete, myPathName)
	addInode(request, state, requiredEntry, myPathName)
}

func compareRegularFile(request *subproto.UpdateRequest, state *state,
	subEntry *filesystem.DirectoryEntry, requiredInode *filesystem.RegularInode,
	myPathName string) (sameType, sameMetadata, sameData bool) {
	if subInode, ok := subEntry.Inode().(*filesystem.RegularInode); ok {
		sameType = true
		sameMetadata = filesystem.CompareRegularInodesMetadata(
			subInode, requiredInode, nil)
		sameData = filesystem.CompareRegularInodesData(subInode,
			requiredInode, os.Stdout)
	}
	return
}

func compareSymlink(request *subproto.UpdateRequest, state *state,
	subEntry *filesystem.DirectoryEntry, requiredInode *filesystem.SymlinkInode,
	myPathName string) (sameType, sameMetadata, sameData bool) {
	if subInode, ok := subEntry.Inode().(*filesystem.SymlinkInode); ok {
		sameType = true
		sameMetadata = filesystem.CompareSymlinkInodesMetadata(subInode,
			requiredInode, nil)
		sameData = filesystem.CompareSymlinkInodesData(subInode, requiredInode,
			os.Stdout)
	}
	return
}

func compareFile(request *subproto.UpdateRequest, state *state,
	subEntry *filesystem.DirectoryEntry, requiredInode *filesystem.Inode,
	myPathName string) (sameType, sameMetadata, sameData bool) {
	if subInode, ok := subEntry.Inode().(*filesystem.Inode); ok {
		sameType = true
		sameMetadata = filesystem.CompareInodesMetadata(subInode, requiredInode,
			nil)
		sameData = filesystem.CompareInodesData(subInode, requiredInode,
			os.Stdout)
	}
	return
}

func compareDirectory(request *subproto.UpdateRequest, state *state,
	subEntry *filesystem.DirectoryEntry,
	requiredInode *filesystem.DirectoryInode,
	myPathName string, filter *filter.Filter) {
	if subInode, ok := subEntry.Inode().(*filesystem.DirectoryInode); ok {
		if filesystem.CompareDirectoriesMetadata(subInode, requiredInode, nil) {
			return
		}
		makeDirectory(request, requiredInode, myPathName, false)
	} else {
		request.PathsToDelete = append(request.PathsToDelete, myPathName)
		makeDirectory(request, requiredInode, myPathName, true)
		fmt.Printf("Replace non-directory: %s...\n", myPathName) // HACK
	}
}

func relink(request *subproto.UpdateRequest, state *state,
	subEntry, requiredEntry *filesystem.DirectoryEntry, myPathName string) {
	subInum, ok := state.requiredInodeToSubInode[requiredEntry.InodeNumber]
	if !ok {
		state.requiredInodeToSubInode[requiredEntry.InodeNumber] =
			subEntry.InodeNumber
		return
	}
	if subInum == subEntry.InodeNumber {
		return
	}
	var hardlink subproto.Hardlink
	hardlink.Source = myPathName
	hardlink.Target = state.subFS.InodeToFilenamesTable[subInum][0]
	request.HardlinksToMake = append(request.HardlinksToMake, hardlink)
	fmt.Printf("Make link: %s => %s\n", hardlink.Source,
		hardlink.Target) // HACK
}

func updateMetadata(request *subproto.UpdateRequest, state *state,
	subEntry, requiredEntry *filesystem.DirectoryEntry, myPathName string) {
	if changed := state.inodesChanged[requiredEntry.InodeNumber]; changed {
		return
	}
	var inode subproto.Inode
	inode.Name = myPathName
	inode.GenericInode = requiredEntry.Inode()
	request.InodesToChange = append(request.InodesToChange, inode)
	state.inodesChanged[requiredEntry.InodeNumber] = true
	fmt.Printf("Update metadata: %s\n", myPathName) // HACK
}

func makeDirectory(request *subproto.UpdateRequest,
	requiredInode *filesystem.DirectoryInode, pathName string, create bool) {
	var newdir subproto.Directory
	newdir.Name = pathName
	newdir.Mode = requiredInode.Mode
	newdir.Uid = requiredInode.Uid
	newdir.Gid = requiredInode.Gid
	if create {
		request.DirectoriesToMake = append(request.DirectoriesToMake, newdir)
		fmt.Printf("Add directory: %s...\n", pathName) // HACK
	} else {
		request.DirectoriesToChange = append(request.DirectoriesToMake, newdir)
		fmt.Printf("Change directory: %s...\n", pathName) // HACK
	}
}

func addInode(request *subproto.UpdateRequest, state *state,
	requiredEntry *filesystem.DirectoryEntry, myPathName string) {
	fmt.Printf("Add entry: %s...\n", myPathName) // HACK
	// TODO(rgooch): Add entry.
}
<commit_msg>Remove some debugging output.<commit_after>package herd

import (
	"fmt"
	"github.com/Symantec/Dominator/lib/filesystem"
	"github.com/Symantec/Dominator/lib/filter"
	subproto "github.com/Symantec/Dominator/proto/sub"
	"path"
	"syscall"
	"time"
)

type state struct {
	requiredInodeToSubInode map[uint64]uint64
	inodesChanged           map[uint64]bool // Required inode number.
	subFS                   *filesystem.FileSystem
	requiredFS              *filesystem.FileSystem
}

func (sub *Sub) buildUpdateRequest(request *subproto.UpdateRequest) {
	fmt.Println("buildUpdateRequest()") // TODO(rgooch): Delete debugging.
	var state state
	state.subFS = &sub.fileSystem.FileSystem
	requiredImage := sub.herd.getImage(sub.requiredImage)
	state.requiredFS = requiredImage.FileSystem
	filter := requiredImage.Filter
	request.Triggers = requiredImage.Triggers
	state.requiredInodeToSubInode = make(map[uint64]uint64)
	state.inodesChanged = make(map[uint64]bool)
	var rusageStart, rusageStop syscall.Rusage
	syscall.Getrusage(syscall.RUSAGE_SELF, &rusageStart)
	compareDirectories(request, &state,
		&state.subFS.DirectoryInode, &state.requiredFS.DirectoryInode,
		"/", filter)
	syscall.Getrusage(syscall.RUSAGE_SELF, &rusageStop) // HACK
	cpuTime := time.Duration(rusageStop.Utime.Sec)*time.Second +
		time.Duration(rusageStop.Utime.Usec)*time.Microsecond -
		time.Duration(rusageStart.Utime.Sec)*time.Second -
		time.Duration(rusageStart.Utime.Usec)*time.Microsecond
	fmt.Printf("Build update request took: %s user CPU time\n", cpuTime)
}

func compareDirectories(request *subproto.UpdateRequest, state *state,
	subDirectory, requiredDirectory *filesystem.DirectoryInode,
	myPathName string, filter *filter.Filter) {
	// First look for entries that should be deleted.
	if subDirectory != nil {
		for name := range subDirectory.EntriesByName {
			pathname := path.Join(myPathName, name)
			if filter.Match(pathname) {
				continue
			}
			if _, ok := requiredDirectory.EntriesByName[name]; !ok {
				request.PathsToDelete = append(request.PathsToDelete, pathname)
				fmt.Printf("Delete: %s\n", pathname) // HACK
			}
		}
	}
	for name, requiredEntry := range requiredDirectory.EntriesByName {
		pathname := path.Join(myPathName, name)
		if filter.Match(pathname) {
			continue
		}
		var subEntry *filesystem.DirectoryEntry
		if subDirectory != nil {
			if se, ok := subDirectory.EntriesByName[name]; ok {
				subEntry = se
			}
		}
		if subEntry == nil {
			addEntry(request, state, requiredEntry, pathname)
		} else {
			compareEntries(request, state, subEntry, requiredEntry, pathname,
				filter)
		}
		// If a directory: descend (possibly with the directory for the sub).
		requiredInode := requiredEntry.Inode()
		if requiredInode, ok := requiredInode.(*filesystem.DirectoryInode); ok {
			var subInode *filesystem.DirectoryInode
			if subEntry != nil {
				if si, ok := subEntry.Inode().(*filesystem.DirectoryInode); ok {
					subInode = si
				}
			}
			compareDirectories(request, state, subInode, requiredInode,
				pathname, filter)
		}
	}
}

func addEntry(request *subproto.UpdateRequest, state *state,
	requiredEntry *filesystem.DirectoryEntry, myPathName string) {
	requiredInode := requiredEntry.Inode()
	if requiredInode, ok := requiredInode.(*filesystem.DirectoryInode); ok {
		makeDirectory(request, requiredInode, myPathName, true)
	} else {
		addInode(request, state, requiredEntry, myPathName)
	}
}

func compareEntries(request *subproto.UpdateRequest, state *state,
	subEntry, requiredEntry *filesystem.DirectoryEntry,
	myPathName string, filter *filter.Filter) {
	var sameType, sameMetadata, sameData bool
	switch requiredInode := requiredEntry.Inode().(type) {
	case *filesystem.RegularInode:
		sameType, sameMetadata, sameData =
			compareRegularFile(request, state, subEntry, requiredInode,
				myPathName)
	case *filesystem.SymlinkInode:
		sameType, sameMetadata, sameData =
			compareSymlink(request, state, subEntry, requiredInode, myPathName)
	case *filesystem.Inode:
		sameType, sameMetadata, sameData =
			compareFile(request, state, subEntry, requiredInode, myPathName)
	case *filesystem.DirectoryInode:
		compareDirectory(request, state, subEntry, requiredInode, myPathName,
			filter)
		return
	default:
		panic("Unsupported entry type")
	}
	if sameType && sameData && sameMetadata {
		relink(request, state, subEntry, requiredEntry, myPathName)
		return
	}
	if sameType && sameData {
		updateMetadata(request, state, subEntry, requiredEntry, myPathName)
		relink(request, state, subEntry, requiredEntry, myPathName)
		return
	}
	request.PathsToDelete = append(request.PathsToDelete, myPathName)
	addInode(request, state, requiredEntry, myPathName)
}

func compareRegularFile(request *subproto.UpdateRequest, state *state,
	subEntry *filesystem.DirectoryEntry, requiredInode *filesystem.RegularInode,
	myPathName string) (sameType, sameMetadata, sameData bool) {
	if subInode, ok := subEntry.Inode().(*filesystem.RegularInode); ok {
		sameType = true
		sameMetadata = filesystem.CompareRegularInodesMetadata(
			subInode, requiredInode, nil)
		sameData = filesystem.CompareRegularInodesData(subInode, requiredInode,
			nil)
	}
	return
}

func compareSymlink(request *subproto.UpdateRequest, state *state,
	subEntry *filesystem.DirectoryEntry, requiredInode *filesystem.SymlinkInode,
	myPathName string) (sameType, sameMetadata, sameData bool) {
	if subInode, ok := subEntry.Inode().(*filesystem.SymlinkInode); ok {
		sameType = true
		sameMetadata = filesystem.CompareSymlinkInodesMetadata(subInode,
			requiredInode, nil)
		sameData = filesystem.CompareSymlinkInodesData(subInode, requiredInode,
			nil)
	}
	return
}

func compareFile(request *subproto.UpdateRequest, state *state,
	subEntry *filesystem.DirectoryEntry, requiredInode *filesystem.Inode,
	myPathName string) (sameType, sameMetadata, sameData bool) {
	if subInode, ok := subEntry.Inode().(*filesystem.Inode); ok {
		sameType = true
		sameMetadata = filesystem.CompareInodesMetadata(subInode, requiredInode,
			nil)
		sameData = filesystem.CompareInodesData(subInode, requiredInode, nil)
	}
	return
}

func compareDirectory(request *subproto.UpdateRequest, state *state,
	subEntry *filesystem.DirectoryEntry,
	requiredInode *filesystem.DirectoryInode,
	myPathName string, filter *filter.Filter) {
	if subInode, ok := subEntry.Inode().(*filesystem.DirectoryInode); ok {
		if filesystem.CompareDirectoriesMetadata(subInode, requiredInode, nil) {
			return
		}
		makeDirectory(request, requiredInode, myPathName, false)
	} else {
		request.PathsToDelete = append(request.PathsToDelete, myPathName)
		makeDirectory(request, requiredInode, myPathName, true)
		fmt.Printf("Replace non-directory: %s...\n", myPathName) // HACK
	}
}

func relink(request *subproto.UpdateRequest, state *state,
	subEntry, requiredEntry *filesystem.DirectoryEntry, myPathName string) {
	subInum, ok := state.requiredInodeToSubInode[requiredEntry.InodeNumber]
	if !ok {
		state.requiredInodeToSubInode[requiredEntry.InodeNumber] =
			subEntry.InodeNumber
		return
	}
	if subInum == subEntry.InodeNumber {
		return
	}
	var hardlink subproto.Hardlink
	hardlink.Source = myPathName
	hardlink.Target = state.subFS.InodeToFilenamesTable[subInum][0]
	request.HardlinksToMake = append(request.HardlinksToMake, hardlink)
	fmt.Printf("Make link: %s => %s\n", hardlink.Source,
		hardlink.Target) // HACK
}

func updateMetadata(request *subproto.UpdateRequest, state *state,
	subEntry, requiredEntry *filesystem.DirectoryEntry, myPathName string) {
	if changed := state.inodesChanged[requiredEntry.InodeNumber]; changed {
		return
	}
	var inode subproto.Inode
	inode.Name = myPathName
	inode.GenericInode = requiredEntry.Inode()
	request.InodesToChange = append(request.InodesToChange, inode)
	state.inodesChanged[requiredEntry.InodeNumber] = true
	fmt.Printf("Update metadata: %s\n", myPathName) // HACK
}

func makeDirectory(request *subproto.UpdateRequest,
	requiredInode *filesystem.DirectoryInode, pathName string, create bool) {
	var newdir subproto.Directory
	newdir.Name = pathName
	newdir.Mode = requiredInode.Mode
	newdir.Uid = requiredInode.Uid
	newdir.Gid = requiredInode.Gid
	if create {
		request.DirectoriesToMake = append(request.DirectoriesToMake, newdir)
		fmt.Printf("Add directory: %s...\n", pathName) // HACK
	} else {
		request.DirectoriesToChange = append(request.DirectoriesToMake, newdir)
		fmt.Printf("Change directory: %s...\n", pathName) // HACK
	}
}

func addInode(request *subproto.UpdateRequest, state *state,
	requiredEntry *filesystem.DirectoryEntry, myPathName string) {
	fmt.Printf("Add entry: %s...\n", myPathName) // HACK
	// TODO(rgooch): Add entry.
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package openstack

import (
	"fmt"
	"time"

	volumeexpand "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions"
	"github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach"
	"k8s.io/apimachinery/pkg/util/wait"
	cpoerrors "k8s.io/cloud-provider-openstack/pkg/util/errors"

	"k8s.io/klog"
)

const (
	VolumeAvailableStatus    = "available"
	VolumeInUseStatus        = "in-use"
	VolumeDeletedStatus      = "deleted"
	VolumeErrorStatus        = "error"
	operationFinishInitDelay = 1 * time.Second
	operationFinishFactor    = 1.1
	operationFinishSteps     = 10
	diskAttachInitDelay      = 1 * time.Second
	diskAttachFactor         = 1.2
	diskAttachSteps          = 15
	diskDetachInitDelay      = 1 * time.Second
	diskDetachFactor         = 1.2
	diskDetachSteps          = 13
	volumeDescription        = "Created by OpenStack Cinder CSI driver"
)

// CreateVolume creates a volume of given size
func (os *OpenStack) CreateVolume(name string, size int, vtype, availability string, snapshotID string, tags *map[string]string) (*volumes.Volume, error) {
	opts := &volumes.CreateOpts{
		Name:             name,
		Size:             size,
		VolumeType:       vtype,
		AvailabilityZone: availability,
		Description:      volumeDescription,
		SnapshotID:       snapshotID,
	}
	if tags != nil {
		opts.Metadata = *tags
	}

	vol, err := volumes.Create(os.blockstorage, opts).Extract()
	if err != nil {
		return nil, err
	}

	return vol, nil
}

// ListVolumes list all the volumes
func (os *OpenStack) ListVolumes() ([]volumes.Volume, error) {

	opts := volumes.ListOpts{}
	pages, err := volumes.List(os.blockstorage, opts).AllPages()
	if err != nil {
		return nil, err
	}
	vols, err := volumes.ExtractVolumes(pages)
	if err != nil {
		return nil, err
	}

	return vols, nil
}

// GetVolumesByName is a wrapper around ListVolumes that creates a Name filter to act as a GetByName
// Returns a list of Volume references with the specified name
func (os *OpenStack) GetVolumesByName(n string) ([]volumes.Volume, error) {
	opts := volumes.ListOpts{Name: n}
	pages, err := volumes.List(os.blockstorage, opts).AllPages()
	if err != nil {
		return nil, err
	}

	vols, err := volumes.ExtractVolumes(pages)
	if err != nil {
		return nil, err
	}

	return vols, nil
}

// DeleteVolume delete a volume
func (os *OpenStack) DeleteVolume(volumeID string) error {
	used, err := os.diskIsUsed(volumeID)
	if err != nil {
		return err
	}
	if used {
		return fmt.Errorf("Cannot delete the volume %q, it's still attached to a node", volumeID)
	}

	err = volumes.Delete(os.blockstorage, volumeID, nil).ExtractErr()
	return err
}

// GetVolume retrieves Volume by its ID.
func (os *OpenStack) GetVolume(volumeID string) (*volumes.Volume, error) {

	vol, err := volumes.Get(os.blockstorage, volumeID).Extract()
	if err != nil {
		return nil, err
	}

	return vol, nil
}

// AttachVolume attaches given cinder volume to the compute
func (os *OpenStack) AttachVolume(instanceID, volumeID string) (string, error) {
	volume, err := os.GetVolume(volumeID)
	if err != nil {
		return "", err
	}

	if len(volume.Attachments) > 0 {
		if instanceID == volume.Attachments[0].ServerID {
			klog.V(4).Infof("Disk %s is already attached to instance %s", volumeID, instanceID)
			return volume.ID, nil
		}
		return "", fmt.Errorf("disk %s is attached to a different instance (%s)", volumeID, volume.Attachments[0].ServerID)
	}

	_, err = volumeattach.Create(os.compute, instanceID, &volumeattach.CreateOpts{
		VolumeID: volume.ID,
	}).Extract()

	if err != nil {
		return "", fmt.Errorf("failed to attach %s volume to %s compute: %v", volumeID, instanceID, err)
	}
	klog.V(2).Infof("Successfully attached %s volume to %s compute", volumeID, instanceID)
	return volume.ID, nil
}

// WaitDiskAttached waits for attched
func (os *OpenStack) WaitDiskAttached(instanceID string, volumeID string) error {
	backoff := wait.Backoff{
		Duration: diskAttachInitDelay,
		Factor:   diskAttachFactor,
		Steps:    diskAttachSteps,
	}

	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
		attached, err := os.diskIsAttached(instanceID, volumeID)
		if err != nil && !cpoerrors.IsNotFound(err) {
			// if this is a race condition indicate the volume is deleted
			// during sleep phase, ignore the error and return attach=false
			return false, err
		}
		return attached, nil
	})

	if err == wait.ErrWaitTimeout {
		err = fmt.Errorf("Volume %q failed to be attached within the alloted time", volumeID)
	}

	return err
}

// DetachVolume detaches given cinder volume from the compute
func (os *OpenStack) DetachVolume(instanceID, volumeID string) error {
	volume, err := os.GetVolume(volumeID)
	if err != nil {
		return err
	}
	if volume.Status == VolumeAvailableStatus {
		klog.V(2).Infof("volume: %s has been detached from compute: %s ", volume.ID, instanceID)
		return nil
	}

	if volume.Status != VolumeInUseStatus {
		return fmt.Errorf("can not detach volume %s, its status is %s", volume.Name, volume.Status)
	}

	if len(volume.Attachments) > 0 {
		if volume.Attachments[0].ServerID != instanceID {
			return fmt.Errorf("disk: %s is not attached to compute: %s", volume.Name, instanceID)
		}
		err = volumeattach.Delete(os.compute, instanceID, volume.ID).ExtractErr()
		if err != nil {
			return fmt.Errorf("failed to delete volume %s from compute %s attached %v", volume.ID, instanceID, err)
		}
		klog.V(2).Infof("Successfully detached volume: %s from compute: %s", volume.ID, instanceID)

	} else {
		return fmt.Errorf("disk: %s has no attachments", volume.Name)
	}

	return nil
}

// WaitDiskDetached waits for detached
func (os *OpenStack) WaitDiskDetached(instanceID string, volumeID string) error {
	backoff := wait.Backoff{
		Duration: diskDetachInitDelay,
		Factor:   diskDetachFactor,
		Steps:    diskDetachSteps,
	}

	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
		attached, err := os.diskIsAttached(instanceID, volumeID)
		if err != nil {
			return false, err
		}
		return !attached, nil
	})

	if err == wait.ErrWaitTimeout {
		err = fmt.Errorf("Volume %q failed to detach within the alloted time", volumeID)
	}

	return err
}

// GetAttachmentDiskPath gets device path of attached volume to the compute
func (os *OpenStack) GetAttachmentDiskPath(instanceID, volumeID string) (string, error) {
	volume, err := os.GetVolume(volumeID)
	if err != nil {
		return "", err
	}
	if volume.Status != VolumeInUseStatus {
		return "", fmt.Errorf("can not get device path of volume %s, its status is %s ", volume.Name, volume.Status)
	}

	if len(volume.Attachments) > 0 && volume.Attachments[0].ServerID != "" {
		if instanceID == volume.Attachments[0].ServerID {
			return volume.Attachments[0].Device, nil
		}
		return "", fmt.Errorf("disk %q is attached to a different compute: %q, should be detached before proceeding", volumeID, volume.Attachments[0].ServerID)
	}
	return "", fmt.Errorf("volume %s has no ServerId", volumeID)
}

// ExpandVolume expands the volume to new size
func (os *OpenStack) ExpandVolume(volumeID string, newSize int) error {
	createOpts := volumeexpand.ExtendSizeOpts{
		NewSize: newSize,
	}
	os.blockstorage.Microversion = "3.42"
	err := volumeexpand.ExtendSize(os.blockstorage, volumeID, createOpts).ExtractErr()
	return err
}

//GetMaxVolLimit returns max vol limit
func (os *OpenStack) GetMaxVolLimit() int64 {
	if os.bsOpts.NodeVolumeAttachLimit > 0 && os.bsOpts.NodeVolumeAttachLimit <= 256 {
		return os.bsOpts.NodeVolumeAttachLimit
	}

	return defaultMaxVolAttachLimit
}

// diskIsAttached queries if a volume is attached to a compute instance
func (os *OpenStack) diskIsAttached(instanceID, volumeID string) (bool, error) {
	volume, err := os.GetVolume(volumeID)
	if err != nil {
		return false, err
	}

	if len(volume.Attachments) > 0 {
		return instanceID == volume.Attachments[0].ServerID, nil
	}

	return false, nil
}

// diskIsUsed returns true a disk is attached to any node.
func (os *OpenStack) diskIsUsed(volumeID string) (bool, error) {
	volume, err := os.GetVolume(volumeID)
	if err != nil {
		return false, err
	}

	if len(volume.Attachments) > 0 {
		return volume.Attachments[0].ServerID != "", nil
	}

	return false, nil
}
<commit_msg>Roll back the microversion, when the resize was made (#786)<commit_after>/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package openstack

import (
	"fmt"
	"time"

	volumeexpand "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions"
	"github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach"
	"k8s.io/apimachinery/pkg/util/wait"
	cpoerrors "k8s.io/cloud-provider-openstack/pkg/util/errors"

	"k8s.io/klog"
)

const (
	VolumeAvailableStatus    = "available"
	VolumeInUseStatus        = "in-use"
	VolumeDeletedStatus      = "deleted"
	VolumeErrorStatus        = "error"
	operationFinishInitDelay = 1 * time.Second
	operationFinishFactor    = 1.1
	operationFinishSteps     = 10
	diskAttachInitDelay      = 1 * time.Second
	diskAttachFactor         = 1.2
	diskAttachSteps          = 15
	diskDetachInitDelay      = 1 * time.Second
	diskDetachFactor         = 1.2
	diskDetachSteps          = 13
	volumeDescription        = "Created by OpenStack Cinder CSI driver"
)

// CreateVolume creates a volume of given size
func (os *OpenStack) CreateVolume(name string, size int, vtype, availability string, snapshotID string, tags *map[string]string) (*volumes.Volume, error) {
	opts := &volumes.CreateOpts{
		Name:             name,
		Size:             size,
		VolumeType:       vtype,
		AvailabilityZone: availability,
		Description:      volumeDescription,
		SnapshotID:       snapshotID,
	}
	if tags != nil {
		opts.Metadata = *tags
	}

	vol, err := volumes.Create(os.blockstorage, opts).Extract()
	if err != nil {
		return nil, err
	}

	return vol, nil
}

// ListVolumes list all the volumes
func (os *OpenStack) ListVolumes() ([]volumes.Volume, error) {

	opts := volumes.ListOpts{}
	pages, err := volumes.List(os.blockstorage, opts).AllPages()
	if err != nil {
		return nil, err
	}
	vols, err := volumes.ExtractVolumes(pages)
	if err != nil {
		return nil, err
	}

	return vols, nil
}

// GetVolumesByName is a wrapper around ListVolumes that creates a Name filter to act as a GetByName
// Returns a list of Volume references with the specified name
func (os *OpenStack) GetVolumesByName(n string) ([]volumes.Volume, error) {
	opts := volumes.ListOpts{Name: n}
	pages, err := volumes.List(os.blockstorage, opts).AllPages()
	if err != nil {
		return nil, err
	}

	vols, err := volumes.ExtractVolumes(pages)
	if err != nil {
		return nil, err
	}

	return vols, nil
}

// DeleteVolume delete a volume
func (os *OpenStack) DeleteVolume(volumeID string) error {
	used, err := os.diskIsUsed(volumeID)
	if err != nil {
		return err
	}
	if used {
		return fmt.Errorf("Cannot delete the volume %q, it's still attached to a node", volumeID)
	}

	err = volumes.Delete(os.blockstorage, volumeID, nil).ExtractErr()
	return err
}

// GetVolume retrieves Volume by its ID.
func (os *OpenStack) GetVolume(volumeID string) (*volumes.Volume, error) {

	vol, err := volumes.Get(os.blockstorage, volumeID).Extract()
	if err != nil {
		return nil, err
	}

	return vol, nil
}

// AttachVolume attaches given cinder volume to the compute
func (os *OpenStack) AttachVolume(instanceID, volumeID string) (string, error) {
	volume, err := os.GetVolume(volumeID)
	if err != nil {
		return "", err
	}

	if len(volume.Attachments) > 0 {
		if instanceID == volume.Attachments[0].ServerID {
			klog.V(4).Infof("Disk %s is already attached to instance %s", volumeID, instanceID)
			return volume.ID, nil
		}
		return "", fmt.Errorf("disk %s is attached to a different instance (%s)", volumeID, volume.Attachments[0].ServerID)
	}

	_, err = volumeattach.Create(os.compute, instanceID, &volumeattach.CreateOpts{
		VolumeID: volume.ID,
	}).Extract()

	if err != nil {
		return "", fmt.Errorf("failed to attach %s volume to %s compute: %v", volumeID, instanceID, err)
	}
	klog.V(2).Infof("Successfully attached %s volume to %s compute", volumeID, instanceID)
	return volume.ID, nil
}

// WaitDiskAttached waits for attched
func (os *OpenStack) WaitDiskAttached(instanceID string, volumeID string) error {
	backoff := wait.Backoff{
		Duration: diskAttachInitDelay,
		Factor:   diskAttachFactor,
		Steps:    diskAttachSteps,
	}

	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
		attached, err := os.diskIsAttached(instanceID, volumeID)
		if err != nil && !cpoerrors.IsNotFound(err) {
			// if this is a race condition indicate the volume is deleted
			// during sleep phase, ignore the error and return attach=false
			return false, err
		}
		return attached, nil
	})

	if err == wait.ErrWaitTimeout {
		err = fmt.Errorf("Volume %q failed to be attached within the alloted time", volumeID)
	}

	return err
}

// DetachVolume detaches given cinder volume from the compute
func (os *OpenStack) DetachVolume(instanceID, volumeID string) error {
	volume, err := os.GetVolume(volumeID)
	if err != nil {
		return err
	}
	if volume.Status == VolumeAvailableStatus {
		klog.V(2).Infof("volume: %s has been detached from compute: %s ", volume.ID, instanceID)
		return nil
	}

	if volume.Status != VolumeInUseStatus {
		return fmt.Errorf("can not detach volume %s, its status is %s", volume.Name, volume.Status)
	}

	if len(volume.Attachments) > 0 {
		if volume.Attachments[0].ServerID != instanceID {
			return fmt.Errorf("disk: %s is not attached to compute: %s", volume.Name, instanceID)
		}
		err = volumeattach.Delete(os.compute, instanceID, volume.ID).ExtractErr()
		if err != nil {
			return fmt.Errorf("failed to delete volume %s from compute %s attached %v", volume.ID, instanceID, err)
		}
		klog.V(2).Infof("Successfully detached volume: %s from compute: %s", volume.ID, instanceID)

	} else {
		return fmt.Errorf("disk: %s has no attachments", volume.Name)
	}

	return nil
}

// WaitDiskDetached waits for detached
func (os *OpenStack) WaitDiskDetached(instanceID string, volumeID string) error {
	backoff := wait.Backoff{
		Duration: diskDetachInitDelay,
		Factor:   diskDetachFactor,
		Steps:    diskDetachSteps,
	}

	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
		attached, err := os.diskIsAttached(instanceID, volumeID)
		if err != nil {
			return false, err
		}
		return !attached, nil
	})

	if err == wait.ErrWaitTimeout {
		err = fmt.Errorf("Volume %q failed to detach within the alloted time", volumeID)
	}

	return err
}

// GetAttachmentDiskPath gets device path of attached volume to the compute
func (os *OpenStack) GetAttachmentDiskPath(instanceID, volumeID string) (string, error) {
	volume, err := os.GetVolume(volumeID)
	if err != nil {
		return "", err
	}
	if volume.Status != VolumeInUseStatus {
		return "", fmt.Errorf("can not get device path of volume %s, its status is %s ", volume.Name, volume.Status)
	}

	if len(volume.Attachments) > 0 && volume.Attachments[0].ServerID != "" {
		if instanceID == volume.Attachments[0].ServerID {
			return volume.Attachments[0].Device, nil
		}
		return "", fmt.Errorf("disk %q is attached to a different compute: %q, should be detached before proceeding", volumeID, volume.Attachments[0].ServerID)
	}
	return "", fmt.Errorf("volume %s has no ServerId", volumeID)
}

// ExpandVolume expands the volume to new size
func (os *OpenStack) ExpandVolume(volumeID string, newSize int) error {
	createOpts := volumeexpand.ExtendSizeOpts{
		NewSize: newSize,
	}

	// save initial microversion
	mv := os.blockstorage.Microversion
	os.blockstorage.Microversion = "3.42"

	err := volumeexpand.ExtendSize(os.blockstorage, volumeID, createOpts).ExtractErr()

	// restore initial microversion
	os.blockstorage.Microversion = mv

	return err
}

//GetMaxVolLimit returns max vol limit
func (os *OpenStack) GetMaxVolLimit() int64 {
	if os.bsOpts.NodeVolumeAttachLimit > 0 && os.bsOpts.NodeVolumeAttachLimit <= 256 {
		return os.bsOpts.NodeVolumeAttachLimit
	}

	return defaultMaxVolAttachLimit
}

// diskIsAttached queries if a volume is attached to a compute instance
func (os *OpenStack) diskIsAttached(instanceID, volumeID string) (bool, error) {
	volume, err := os.GetVolume(volumeID)
	if err != nil {
		return false, err
	}

	if len(volume.Attachments) > 0 {
		return instanceID == volume.Attachments[0].ServerID, nil
	}

	return false, nil
}

// diskIsUsed returns true a disk is attached to any node.
func (os *OpenStack) diskIsUsed(volumeID string) (bool, error) {
	volume, err := os.GetVolume(volumeID)
	if err != nil {
		return false, err
	}

	if len(volume.Attachments) > 0 {
		return volume.Attachments[0].ServerID != "", nil
	}

	return false, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/sha1"
	"encoding/binary"
	"fmt"
	"image"
	"image/gif"
	"log"
	"os"
)

const maxCharge = 6

func main() {

	inputFileName := os.Args[1]
	outputFileName := os.Args[2]

	in, err := os.Open(inputFileName)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	gifImage, err := gif.DecodeAll(in)
	if err != nil {
		log.Fatal(err)
	}

	gifImage.Image = gifImage.Image[:1]
	gifImage.Delay = gifImage.Delay[:1]
	gifImage.Disposal = gifImage.Disposal[:1]

	img := gifImage.Image[0]

	circuit := NewCircuit(img)

	transparentColorIndex := uint8(0)
	for index, color := range img.Palette {
		if _, _, _, a := color.RGBA(); a != 0 {
			continue
		}
		transparentColorIndex = uint8(index)
		break
	}

	loopingHash := circuit.FindLoopingHash()
	circuit.Simulate()
	circuit.Draw(gifImage.Image[0])
	for !bytes.Equal(loopingHash[:], circuit.Hash()) {
		img := image.NewPaletted(img.Bounds(), img.Palette)
		palettedFill(img, transparentColorIndex)
		circuit.Simulate()
		circuit.DiffDraw(img)
		gifImage.Image = append(gifImage.Image, img)
		gifImage.Delay = append(gifImage.Delay, 1)
		gifImage.Disposal = append(gifImage.Disposal, 0)
	}

	out, err := os.Create(outputFileName)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	err = gif.EncodeAll(out, gifImage)
	if err != nil {
		log.Fatal(err)
	}
}

func palettedFill(img *image.Paletted, index uint8) {
	bounds := img.Bounds()
	for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
		for x := bounds.Min.X; x < bounds.Max.X; x++ {
			img.SetColorIndex(x, y, index)
		}
	}
}

type Circuit struct {
	wires       []*wire
	transistors []*transistor
}

func NewCircuit(img *image.Paletted) Circuit {
	size := img.Bounds().Size()
	groups := make(map[*group]struct{}, 0)
	matrix := newBucketMatrix(size.X, size.Y)
	for y := 0; y < size.Y; y++ {
		for x := 0; x < size.X; x++ {
			r := img.ColorIndexAt(x, y)
			if r <= maxCharge {
				topLeftBucket := matrix.get(x-1, y-1)
				topBucket := matrix.get(x, y-1)
				leftBucket := matrix.get(x-1, y)
				var currentBucket *bucket
				switch {
				case nil == topBucket && nil == leftBucket:
					currentBucket = newBucket()
					groups[currentBucket.group] = struct{}{}
				case nil == topBucket && nil != leftBucket:
					currentBucket = leftBucket
				case (nil != topBucket && nil == leftBucket) ||
					topBucket == leftBucket ||
					topBucket.group == leftBucket.group:
					currentBucket = topBucket
				default:
					currentBucket = topBucket
					delete(groups, topBucket.group)
					topBucket.group.moveBucketsTo(leftBucket.group)
				}
				if nil != topLeftBucket && nil != topBucket && nil != leftBucket {
					currentBucket.group.wire.isPowerSource = true
					currentBucket.group.wire.charge = maxCharge
					currentBucket.group.wire.previousCharge = maxCharge
				}
				matrix.set(x, y, currentBucket)
				currentBucket.addPixel(image.Point{x, y})
				if r > currentBucket.group.wire.charge {
					currentBucket.group.wire.charge = r
					currentBucket.group.wire.previousCharge = r
				}
			}
		}
	}

	for y := 0; y < size.Y; y++ {
		for x := 0; x < size.X; x++ {
			if nil != matrix.get(x, y) {
				continue
			}
			topBucket := matrix.get(x, y-1)
			topRightBucket := matrix.get(x+1, y-1)
			rightBucket := matrix.get(x+1, y)
			bottomRightBucket := matrix.get(x+1, y+1)
			bottomBucket := matrix.get(x, y+1)
			bottomLeftBucket := matrix.get(x-1, y+1)
			leftBucket := matrix.get(x-1, y)
			topLeftBucket := matrix.get(x-1, y-1)
			if nil == topLeftBucket && nil == topRightBucket && nil == bottomLeftBucket && nil == bottomRightBucket &&
				nil != topBucket && nil != rightBucket && nil != bottomBucket && nil != leftBucket {
				delete(groups, topBucket.group)
				topBucket.group.moveBucketsTo(bottomBucket.group)
				delete(groups, leftBucket.group)
				leftBucket.group.moveBucketsTo(rightBucket.group)
			}
		}
	}

	transistors := make([]*transistor, 0)
	for y := 0; y < size.Y; y++ {
		for x := 0; x < size.X; x++ {
			if nil != matrix.get(x, y) {
				continue
			}
			topBucket := matrix.get(x, y-1)
			topRightBucket := matrix.get(x+1, y-1)
			rightBucket := matrix.get(x+1, y)
			bottomRightBucket := matrix.get(x+1, y+1)
			bottomBucket := matrix.get(x, y+1)
			bottomLeftBucket := matrix.get(x-1, y+1)
			leftBucket := matrix.get(x-1, y)
			topLeftBucket := matrix.get(x-1, y-1)

			switch {
			case nil == bottomLeftBucket && nil == bottomRightBucket &&
				nil == topBucket && nil != rightBucket && nil != bottomBucket && nil != leftBucket:
				transistors = append(transistors,
					newTransistor(image.Point{x, y}, bottomBucket.group.wire, rightBucket.group.wire, leftBucket.group.wire))
			case nil == bottomLeftBucket && nil == topLeftBucket &&
				nil != topBucket && nil == rightBucket && nil != bottomBucket && nil != leftBucket:
				transistors = append(transistors,
					newTransistor(image.Point{x, y}, leftBucket.group.wire, topBucket.group.wire, bottomBucket.group.wire))
			case nil == topLeftBucket && nil == topRightBucket &&
				nil != topBucket && nil != rightBucket && nil == bottomBucket && nil != leftBucket:
				transistors = append(transistors,
					newTransistor(image.Point{x, y}, topBucket.group.wire, rightBucket.group.wire, leftBucket.group.wire))
			case nil == bottomRightBucket && nil == topRightBucket &&
				nil != topBucket && nil != rightBucket && nil != bottomBucket && nil == leftBucket:
				transistors = append(transistors,
					newTransistor(image.Point{x, y}, rightBucket.group.wire, topBucket.group.wire, bottomBucket.group.wire))
			}
		}
	}

	wires := make([]*wire, len(groups))
	i := 0
	for k := range groups {
		wires[i] = k.wire
		i++
	}

	return Circuit{wires: wires, transistors: transistors}
}

func (c *Circuit) Simulate() {
	for _, wire := range c.wires {
		if wire.isPowerSource {
			continue
		}
		wire.previousCharge = wire.charge
	}
	for _, wire := range c.wires {
		if wire.isPowerSource {
			continue
		}
		source := wire.tracePowerSource()
		if source.previousCharge > wire.previousCharge+1 {
			wire.charge++
		} else if source.previousCharge <= wire.previousCharge && wire.previousCharge > 0 {
			wire.charge--
		}
	}
}

func (c *Circuit) DiffDraw(img *image.Paletted) {
	for _, wire := range c.wires {
		if wire.previousCharge == wire.charge {
			continue
		}
		wire.draw(img, wire.charge)
	}
}

func (c *Circuit) Draw(img *image.Paletted) {
	for _, wire := range c.wires {
		wire.draw(img, wire.charge)
	}
	for _, transistor := range c.transistors {
		transistor.draw(img, 9)
	}
}

func (c *Circuit) FindLoopingHash() []byte {
	hashs := make(map[[sha1.Size]byte]struct{}, 0)
	for {
		c.Simulate()
		var hash [sha1.Size]byte
		copy(hash[:], c.Hash())
		if _, ok := hashs[hash]; ok {
			return hash[:]
		}
		hashs[hash] = struct{}{}
	}
}

func (c *Circuit) Hash() []byte {
	hash := sha1.New()
	for index, wire := range c.wires {
		buf := new(bytes.Buffer)

		err := binary.Write(buf, binary.LittleEndian, uint32(index))
		if err != nil {
			log.Fatal(err)
		}
		err = binary.Write(buf, binary.LittleEndian, wire.charge)
		if err != nil {
			log.Fatal(err)
		}
		_, err = hash.Write(buf.Bytes())
		if err != nil {
			log.Fatal(err)
		}
	}
	return hash.Sum(nil)
}

type transistor struct {
	position image.Point
	base     *wire
	inputA   *wire
	inputB   *wire
}

func newTransistor(position image.Point, base, inputA, inputB *wire) *transistor {
	transistor := &transistor{
		position: position,
		base:     base,
		inputA:   inputA,
		inputB:   inputB,
	}
	inputA.transistors = append(inputA.transistors, transistor)
	inputB.transistors = append(inputB.transistors, transistor)
	return transistor
}

func (t *transistor) draw(img *image.Paletted, colorIndex uint8) {
	img.SetColorIndex(t.position.X, t.position.Y, colorIndex)
}

type wire struct {
	pixels         []image.Point
	bounds         image.Rectangle
	transistors    []*transistor
	isPowerSource  bool
	charge         uint8
	previousCharge uint8
}

func newWire() *wire {
	return &wire{
		pixels:         make([]image.Point, 0),
		bounds:         image.Rectangle{image.Pt(0, 0), image.Pt(0, 0)},
		transistors:    make([]*transistor, 0),
		isPowerSource:  false,
		charge:         0,
		previousCharge: 0,
	}
}

func (w *wire) draw(img *image.Paletted, colorIndex uint8) {
	for _, pixel := range w.pixels {
		img.SetColorIndex(pixel.X, pixel.Y, colorIndex)
	}
}

func (w *wire) tracePowerSource() *wire {
	result := w
	for _, transistor := range w.transistors {
		if nil != transistor.base && transistor.base.previousCharge > 0 {
			continue
		}
		if w == transistor.inputA {
			if transistor.inputB.isPowerSource {
				return transistor.inputB
			}
			if transistor.inputB.previousCharge > result.previousCharge {
				result = transistor.inputB
				continue
			}
		} else if w == transistor.inputB {
			if transistor.inputA.isPowerSource {
				return transistor.inputA
			}
			if transistor.inputA.previousCharge > result.previousCharge {
				result = transistor.inputA
				continue
			}
		}
	}
	return result
}

type bucketMatrix struct {
	buckets [][]*bucket
	width   int
	height  int
}

func newBucketMatrix(width int, height int) *bucketMatrix {
	m := &bucketMatrix{make([][]*bucket, height), width, height}
	for y := 0; y < height; y++ {
		m.buckets[y] = make([]*bucket, width)
	}
	return m
}

func (m *bucketMatrix) get(x int, y int) *bucket {
	if x < 0 || y < 0 || x >= m.width || y >= m.height {
		return nil
	}
	return m.buckets[y][x]
}

func (m *bucketMatrix) set(x int, y int, bucket *bucket) {
	m.buckets[y][x] = bucket
}

type bucket struct {
	group *group
}

func newBucket() *bucket {

	newBucket := &bucket{nil}
	newGroup := &group{
		buckets: []*bucket{newBucket},
		wire:    newWire(),
	}
	newBucket.group = newGroup
	return newBucket
}

func (b *bucket) addPixel(pixel image.Point) {
	b.group.wire.pixels = append(b.group.wire.pixels, pixel)
	b.group.wire.bounds = b.group.wire.bounds.Union(
		image.Rectangle{
			pixel,
			pixel.Add(image.Point{1, 1})})
}

type group struct {
	buckets []*bucket
	wire    *wire
}

func (g *group) moveBucketsTo(other *group) {
	for _, bucket := range g.buckets {
		bucket.group = other
		other.buckets = append(other.buckets, bucket)
	}
	if g.wire.isPowerSource {
		other.wire.isPowerSource = true
		other.wire.charge = maxCharge
		other.wire.previousCharge = maxCharge
	}
	other.wire.bounds = other.wire.bounds.Union(g.wire.bounds)
	other.wire.pixels = append(other.wire.pixels, g.wire.pixels...)
}
<commit_msg>added wireState struct to make the wire struct stateless<commit_after>package main

import (
	"bytes"
	"crypto/sha1"
	"encoding/binary"
	"fmt"
	"image"
	"image/gif"
	"log"
	"os"
)

const maxCharge = 6

func main() {

	inputFileName := os.Args[1]
	outputFileName := os.Args[2]

	in, err := os.Open(inputFileName)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	gifImage, err := gif.DecodeAll(in)
	if err != nil {
		log.Fatal(err)
	}

	gifImage.Image = gifImage.Image[:1]
	gifImage.Delay = gifImage.Delay[:1]
	gifImage.Disposal = gifImage.Disposal[:1]

	img := gifImage.Image[0]

	transparentColorIndex := uint8(0)
	for index, color := range img.Palette {
		if _, _, _, a := color.RGBA(); a != 0 {
			continue
		}
		transparentColorIndex = uint8(index)
		break
	}

	simulation, loopingHash := NewSimulation(img).FindLooping()
	simulation = simulation.Step()
	simulation.Draw(gifImage.Image[0])
	for !bytes.Equal(loopingHash[:], simulation.Hash()) {
		img := image.NewPaletted(img.Bounds(), img.Palette)
		palettedFill(img, transparentColorIndex)
		newSimulation := simulation.Step()
		newSimulation.DiffDraw(simulation, img)
		simulation = newSimulation
		gifImage.Image = append(gifImage.Image, img)
		gifImage.Delay = append(gifImage.Delay, 1)
		gifImage.Disposal = append(gifImage.Disposal, 0)
	}

	out, err := os.Create(outputFileName)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	err = gif.EncodeAll(out, gifImage)
	if err != nil {
		log.Fatal(err)
	}
}

func palettedFill(img *image.Paletted, index uint8) {
	bounds := img.Bounds()
	for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
		for x := bounds.Min.X; x < bounds.Max.X; x++ {
			img.SetColorIndex(x, y, index)
		}
	}
}

type circuit struct {
	wires       []*wire
	transistors []*transistor
}

type wireState struct {
	charge uint8
	wire   *wire
}

type Simulation struct {
	circuit *circuit
	states  []wireState
}

func NewSimulation(img *image.Paletted) *Simulation {
	size := img.Bounds().Size()
	groups := make(map[*group]struct{}, 0)
	matrix := newBucketMatrix(size.X, size.Y)
	for y := 0; y < size.Y; y++ {
		for x := 0; x < size.X; x++ {
			if img.ColorIndexAt(x, y) <= maxCharge {
				topLeftBucket := matrix.get(x-1, y-1)
				topBucket := matrix.get(x, y-1)
				leftBucket := matrix.get(x-1, y)
				var currentBucket *bucket
				switch {
				case nil == topBucket && nil == leftBucket:
					currentBucket = newBucket()
					groups[currentBucket.group] = struct{}{}
				case nil == topBucket && nil != leftBucket:
					currentBucket = leftBucket
				case (nil != topBucket && nil == leftBucket) ||
					topBucket == leftBucket ||
					topBucket.group == leftBucket.group:
					currentBucket = topBucket
				default:
					currentBucket = topBucket
					delete(groups, topBucket.group)
					topBucket.group.moveBucketsTo(leftBucket.group)
				}
				if nil != topLeftBucket && nil != topBucket && nil != leftBucket {
					currentBucket.group.wire.isPowerSource = true
				}
				matrix.set(x, y, currentBucket)
				currentBucket.addPixel(image.Point{x, y})
			}
		}
	}

	for y := 0; y < size.Y; y++ {
		for x := 0; x < size.X; x++ {
			if nil != matrix.get(x, y) {
				continue
			}
			topBucket := matrix.get(x, y-1)
			topRightBucket := matrix.get(x+1, y-1)
			rightBucket := matrix.get(x+1, y)
			bottomRightBucket := matrix.get(x+1, y+1)
			bottomBucket := matrix.get(x, y+1)
			bottomLeftBucket := matrix.get(x-1, y+1)
			leftBucket := matrix.get(x-1, y)
			topLeftBucket := matrix.get(x-1, y-1)
			if nil == topLeftBucket && nil == topRightBucket && nil == bottomLeftBucket && nil == bottomRightBucket &&
				nil != topBucket && nil != rightBucket && nil != bottomBucket && nil != leftBucket {
				delete(groups, topBucket.group)
				topBucket.group.moveBucketsTo(bottomBucket.group)
				delete(groups, leftBucket.group)
				leftBucket.group.moveBucketsTo(rightBucket.group)
			}
		}
	}

	transistors := make([]*transistor, 0)
	for y := 0; y < size.Y; y++ {
		for x := 0; x < size.X; x++ {
			if nil != matrix.get(x, y) {
				continue
			}
			topBucket := matrix.get(x, y-1)
			topRightBucket := matrix.get(x+1, y-1)
			rightBucket := matrix.get(x+1, y)
			bottomRightBucket := matrix.get(x+1, y+1)
			bottomBucket := matrix.get(x, y+1)
			bottomLeftBucket := matrix.get(x-1, y+1)
			leftBucket := matrix.get(x-1, y)
			topLeftBucket := matrix.get(x-1, y-1)

			switch {
			case nil == bottomLeftBucket && nil == bottomRightBucket &&
				nil == topBucket && nil != rightBucket && nil != bottomBucket && nil != leftBucket:
				transistors = append(transistors,
					newTransistor(image.Point{x, y}, bottomBucket.group.wire, rightBucket.group.wire, leftBucket.group.wire))
			case nil == bottomLeftBucket && nil == topLeftBucket &&
				nil != topBucket && nil == rightBucket && nil != bottomBucket && nil != leftBucket:
				transistors = append(transistors,
					newTransistor(image.Point{x, y}, leftBucket.group.wire, topBucket.group.wire, bottomBucket.group.wire))
			case nil == topLeftBucket && nil == topRightBucket &&
				nil != topBucket && nil != rightBucket && nil == bottomBucket && nil != leftBucket:
				transistors = append(transistors,
					newTransistor(image.Point{x, y}, topBucket.group.wire, rightBucket.group.wire, leftBucket.group.wire))
			case nil == bottomRightBucket && nil == topRightBucket &&
				nil != topBucket && nil != rightBucket && nil != bottomBucket && nil == leftBucket:
				transistors = append(transistors,
					newTransistor(image.Point{x, y}, rightBucket.group.wire, topBucket.group.wire, bottomBucket.group.wire))
			}
		}
	}

	wires := make([]*wire, len(groups))
	wireStates := make([]wireState, len(groups))
	i := 0
	for k := range groups {
		k.wire.index = i
		wires[i] = k.wire
		var charge uint8
		if k.wire.isPowerSource {
			charge = maxCharge
		} else {
			charge = 0
		}
		wireStates[i] = wireState{charge, k.wire}
		i++
	}

	return &Simulation{&circuit{wires: wires, transistors: transistors}, wireStates}
}

func (s *Simulation) Step() *Simulation {
	newWireState := make([]wireState, len(s.states))
	for i, state := range s.states {
		charge := state.charge
		if !state.wire.isPowerSource {
			source := s.tracePowerSource(state)
			if source.charge > state.charge+1 {
				charge = state.charge + 1
			} else if source.charge <= state.charge && state.charge > 0 {
				charge = state.charge - 1
			}
		}
		newWireState[i] = wireState{charge, state.wire}
	}
	return &Simulation{s.circuit, newWireState}
}

func (s *Simulation) tracePowerSource(origin wireState) wireState {
	result := origin
	for _, transistor := range origin.wire.transistors {
		if nil != transistor.base && s.states[transistor.base.index].charge > 0 {
			continue
		}
		if origin.wire == transistor.inputA {
			inputBState := s.states[transistor.inputB.index]
			if transistor.inputB.isPowerSource {
				return inputBState
			}
			if inputBState.charge > result.charge {
				result = inputBState
				continue
			}
		} else if origin.wire == transistor.inputB {
			inputAState := s.states[transistor.inputA.index]
			if transistor.inputA.isPowerSource {
				return inputAState
			}
			if inputAState.charge > result.charge {
				result = inputAState
				continue
			}
		}
	}
	return result
}

func (s *Simulation) DiffDraw(previousSimulation *Simulation, img *image.Paletted) {
	for i, state := range s.states {
		if previousSimulation.states[i].charge == state.charge {
			continue
		}
		state.wire.draw(img, state.charge)
	}
}

func (s *Simulation) Draw(img *image.Paletted) {
	for _, state := range s.states {
		state.wire.draw(img, state.charge)
	}
	for _, transistor := range s.circuit.transistors {
		transistor.draw(img, 9)
	}
}

func (s *Simulation) FindLooping() (*Simulation, []byte) {
	hashs := make(map[[sha1.Size]byte]struct{}, 0)
	for {
		s = s.Step()
		var hash [sha1.Size]byte
		copy(hash[:], s.Hash())
		if _, ok := hashs[hash]; ok {
			return s, hash[:]
		}
		hashs[hash] = struct{}{}
	}
}

func (s *Simulation) Hash() []byte {
	hash := sha1.New()

	for index, state := range s.states {
		buf := new(bytes.Buffer)

		err := binary.Write(buf, binary.LittleEndian, uint32(index))
		if err != nil {
			log.Fatal(err)
		}
		err = binary.Write(buf, binary.LittleEndian, state.charge)
		if err != nil {
			log.Fatal(err)
		}
		_, err = hash.Write(buf.Bytes())
		if err != nil {
			log.Fatal(err)
		}
	}
	return hash.Sum(nil)
}

type transistor struct {
	position image.Point
	base     *wire
	inputA   *wire
	inputB   *wire
}

func newTransistor(position image.Point, base, inputA, inputB *wire) *transistor {
	transistor := &transistor{
		position: position,
		base:     base,
		inputA:   inputA,
		inputB:   inputB,
	}
	inputA.transistors = append(inputA.transistors, transistor)
	inputB.transistors = append(inputB.transistors, transistor)
	return transistor
}

func (t *transistor) draw(img *image.Paletted, colorIndex uint8) {
	img.SetColorIndex(t.position.X, t.position.Y, colorIndex)
}

type wire struct {
	index         int
	pixels        []image.Point
	bounds        image.Rectangle
	transistors   []*transistor
	isPowerSource bool
}

func newWire() *wire {
	return &wire{
		index:         -1,
		pixels:        make([]image.Point, 0),
		bounds:        image.Rectangle{image.Pt(0, 0), image.Pt(0, 0)},
		transistors:   make([]*transistor, 0),
		isPowerSource: false,
	}
}

func (w *wire) draw(img *image.Paletted, colorIndex uint8) {
	for _, pixel := range w.pixels {
		img.SetColorIndex(pixel.X, pixel.Y, colorIndex)
	}
}

type bucketMatrix struct {
	buckets [][]*bucket
	width   int
	height  int
}

func newBucketMatrix(width int, height int) *bucketMatrix {
	m := &bucketMatrix{make([][]*bucket, height), width, height}
	for y := 0; y < height; y++ {
		m.buckets[y] = make([]*bucket, width)
	}
	return m
}

func (m *bucketMatrix) get(x int, y int) *bucket {
	if x < 0 || y < 0 || x >= m.width || y >= m.height {
		return nil
	}
	return m.buckets[y][x]
}

func (m *bucketMatrix) set(x int, y int, bucket *bucket) {
	m.buckets[y][x] = bucket
}

type bucket struct {
	group *group
}

func newBucket() *bucket {

	newBucket := &bucket{nil}
	newGroup := &group{
		buckets: []*bucket{newBucket},
		wire:    newWire(),
	}
	newBucket.group = newGroup
	return newBucket
}

func (b *bucket) addPixel(pixel image.Point) {
	b.group.wire.pixels = append(b.group.wire.pixels, pixel)
	b.group.wire.bounds = b.group.wire.bounds.Union(
		image.Rectangle{
			pixel,
			pixel.Add(image.Point{1, 1})})
}

type group struct {
	buckets []*bucket
	wire    *wire
}

func (g *group) moveBucketsTo(other *group) {
	for _, bucket := range g.buckets {
		bucket.group = other
		other.buckets = append(other.buckets, bucket)
	}
	if g.wire.isPowerSource {
		other.wire.isPowerSource = true
	}
	other.wire.bounds = other.wire.bounds.Union(g.wire.bounds)
	other.wire.pixels = append(other.wire.pixels, g.wire.pixels...)
}
<|endoftext|>


<commit_before>package main

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
)

func TestAWSS3GetArtifacts(t *testing.T) {
	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/decap-build-artifacts/buildID" {
			t.Fatalf("Want /decap-build-artifacts/buildID but got %s\n", r.URL.Path)
		}
		w.Write([]byte{0})
	}))
	defer testServer.Close()

	config := aws.NewConfig().WithCredentials(credentials.NewStaticCredentials("key", "secret", "")).WithRegion("region").WithMaxRetries(3).WithEndpoint(testServer.URL).WithS3ForcePathStyle(true)

	c := AWSStorageService{config}
	data, err := c.GetArtifacts("buildID")
	if err != nil {
		t.Fatal(err)
	}
	if len(data) != 1 {
		t.Fatalf("Want 1 but got %d\n", len(data))
	}
	if data[0] != 0 {
		t.Fatalf("Want 0 but got %d\n", data[0])
	}
}

func TestAWSS3GetConsoleLogs(t *testing.T) {
	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/decap-console-logs/buildID" {
			t.Fatalf("Want /decap-console-logs/buildID but got %s\n", r.URL.Path)
		}
		w.Write([]byte{0})
	}))
	defer testServer.Close()

	config := aws.NewConfig().WithCredentials(credentials.NewStaticCredentials("key", "secret", "")).WithRegion("region").WithMaxRetries(3).WithEndpoint(testServer.URL).WithS3ForcePathStyle(true)

	c := AWSStorageService{config}
	data, err := c.GetConsoleLog("buildID")
	if err != nil {
		t.Fatal(err)
	}
	if len(data) != 1 {
		t.Fatalf("Want 1 but got %d\n", len(data))
	}
	if data[0] != 0 {
		t.Fatalf("Want 0 but got %d\n", data[0])
	}
}
<commit_msg>add dynamodb get-builds test<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
)

func TestAWSS3GetArtifacts(t *testing.T) {
	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/decap-build-artifacts/buildID" {
			t.Fatalf("Want /decap-build-artifacts/buildID but got %s\n", r.URL.Path)
		}
		w.Write([]byte{0})
	}))
	defer testServer.Close()

	config := aws.NewConfig().WithCredentials(credentials.NewStaticCredentials("key", "secret", "")).WithRegion("region").WithMaxRetries(3).WithEndpoint(testServer.URL).WithS3ForcePathStyle(true)

	c := AWSStorageService{config}
	data, err := c.GetArtifacts("buildID")
	if err != nil {
		t.Fatal(err)
	}
	if len(data) != 1 {
		t.Fatalf("Want 1 but got %d\n", len(data))
	}
	if data[0] != 0 {
		t.Fatalf("Want 0 but got %d\n", data[0])
	}
}

func TestAWSS3GetConsoleLogs(t *testing.T) {
	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/decap-console-logs/buildID" {
			t.Fatalf("Want /decap-console-logs/buildID but got %s\n", r.URL.Path)
		}
		w.Write([]byte{0})
	}))
	defer testServer.Close()

	config := aws.NewConfig().WithCredentials(credentials.NewStaticCredentials("key", "secret", "")).WithRegion("region").WithMaxRetries(3).WithEndpoint(testServer.URL).WithS3ForcePathStyle(true)

	c := AWSStorageService{config}
	data, err := c.GetConsoleLog("buildID")
	if err != nil {
		t.Fatal(err)
	}
	if len(data) != 1 {
		t.Fatalf("Want 1 but got %d\n", len(data))
	}
	if data[0] != 0 {
		t.Fatalf("Want 0 but got %d\n", data[0])
	}
}

func TestDynamoDbGetBuilds(t *testing.T) {

	type F struct {
		AttrV struct {
			Key struct {
				S string `json:"S"`
			} `json:":pkey"`
			Since struct {
				N string `json:"N"`
			} `json:":since"`
		} `json:"ExpressionAttributeValues"`
		IndexName              string `json:"IndexName"`
		KeyConditionExpression string `json:"KeyConditionExpression"`
		Limit                  int    `json:"Limit"`
		ScanIndexForward       bool   `json:"ScanIndexForward"`
		TableName              string `json:"TableName"`
	}

	var v F
	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		body, _ := ioutil.ReadAll(r.Body)
		err := json.Unmarshal(body, &v)
		if err != nil {
			t.Fatal(err)
		}

		fmt.Fprintf(w,
			`{
    "builds": [
        {
            "branch": "master",
            "duration": 1,
            "id": "c8846985-0bda-4d5a-92f1-586b012d5105",
            "result": 0,
            "unixtime": 1442792985
        },
        {
            "branch": "master",
            "duration": 0,
            "id": "3f5f0b9b-a5eb-4197-acd5-fb0e677c65c3",
            "result": 0,
            "unixtime": 1442788846
        },
        {
            "branch": "master",
            "duration": 0,
            "id": "4dad3d9d-75c1-414c-8126-b5b77f56281d",
            "result": 0,
            "unixtime": 1442788582
        }
    ]
}`)
	}))
	defer testServer.Close()

	config := aws.NewConfig().WithCredentials(credentials.NewStaticCredentials("key", "secret", "")).WithRegion("region").WithMaxRetries(3).WithEndpoint(testServer.URL)
	c := AWSStorageService{config}

	_, err := c.GetBuildsByProject(Project{Team: "ae6rt", Library: "somelib"}, 0, 1)
	if err != nil {
		t.Fatal(err)
	}

	if v.AttrV.Key.S != "ae6rt/somelib" {
		t.Fatalf("Want ae6rt/somelib but got %s\n", v.AttrV.Key.S)
	}
	if v.AttrV.Since.N != "0" {
		t.Fatalf("Want 0 but got %s\n", v.AttrV.Since.N)
	}
	if v.IndexName != "projectKey-buildTime-index" {
		t.Fatalf("Want projectKey-buildTime-index but got %s\n", v.IndexName)
	}
	if v.KeyConditionExpression != "projectKey = :pkey and buildTime > :since" {
		t.Fatalf("Want projectKey = :pkey and buildTime > :since but got %s\n", v.KeyConditionExpression)
	}
	if v.Limit != 1 {
		t.Fatalf("Want 1 but got %d\n", v.Limit)
	}
	if v.ScanIndexForward {
		t.Fatal("Want false")
	}
	if v.TableName != "decap-build-metadata" {
		t.Fatal("Want decap-build-metadata but got %s\n", v.TableName)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"time"

	"github.com/garyburd/redigo/redis"
	"github.com/lancetw/hcfd-forecast/db"
	"github.com/lancetw/hcfd-forecast/rain"
	"github.com/line/line-bot-sdk-go/linebot"
	"github.com/robfig/cron"
)

const timeZone = "Asia/Taipei"

var bot *linebot.Client

func main() {
	c := cron.New()
	c.AddFunc("0 */3 * * * *", GoProcess)
	c.Start()

	for {
		time.Sleep(10000000000000)
		fmt.Println("sleep")
	}
}

// GoProcess is main process
func GoProcess() {
	strID := os.Getenv("ChannelID")
	numID, err := strconv.ParseInt(strID, 10, 64)
	if err != nil {
		log.Fatal("Wrong environment setting about ChannelID")
	}
	bot, err = linebot.NewClient(numID, os.Getenv("ChannelSecret"), os.Getenv("MID"))
	if err != nil {
		log.Println("Bot:", bot, " err:", err)
	}

	for {
		log.Println("=== 查詢。開始 ===")

		c := db.Connect(os.Getenv("REDISTOGO_URL"))

		targets0 := []string{"新竹市"}
		msgs0, token0 := rain.GetRainingInfo(targets0, false)

		if token0 != "" {
			status0, getErr := redis.Int(c.Do("SISMEMBER", "token0", token0))
			if getErr != nil {
				log.Println(getErr)
			}

			if status0 == 0 {
				users0, smembersErr := redis.Strings(c.Do("SMEMBERS", "user"))

				if smembersErr != nil {
					log.Println("GetRainingInfo SMEMBERS redis error", smembersErr)
				} else {
					local := time.Now()
					location, timeZoneErr := time.LoadLocation(timeZone)
					if timeZoneErr == nil {
						local = local.In(location)
					}

					if len(msgs0) > 0 {
						var text string
						for _, msg := range msgs0 {
							text = text + msg + "\n\n"
						}
						for _, contentTo := range users0 {
							_, err = bot.SendText([]string{contentTo}, text)
							if err != nil {
								log.Println(err)
							}
						}
					}
				}
			}

			n0, addErr := c.Do("SADD", "token0", token0)
			if addErr != nil {
				log.Println("GetRainingInfo SADD to redis error", addErr, n0)
			}
		}

		targets1 := []string{"新竹市", "新竹縣"}
		msgs1, token1 := rain.GetWarningInfo(targets1)

		if token1 != "" {
			status1, getErr := redis.Int(c.Do("SISMEMBER", "token1", token1))
			if getErr != nil {
				log.Println(getErr)
			}

			if status1 == 0 {
				users1, smembersErr := redis.Strings(c.Do("SMEMBERS", "user"))

				if smembersErr != nil {
					log.Println("GetWarningInfo SMEMBERS redis error", smembersErr)
				} else {
					local := time.Now()
					location, locationErr := time.LoadLocation(timeZone)
					if locationErr == nil {
						local = local.In(location)
					}
					for _, contentTo := range users1 {
						for _, msg := range msgs1 {
							_, msgErr := bot.SendText([]string{contentTo}, msg)
							if msgErr != nil {
								log.Println(err)
							}
						}
					}
				}
			}
		}

		if token1 != "" {
			n, addErr := c.Do("SADD", "token1", token1)
			if addErr != nil {
				log.Println("GetWarningInfo SADD to redis error", addErr, n)
			}
		}

		defer c.Close()

		log.Println("=== 查詢。結束 ===")

		time.Sleep(60 * time.Second)
	}
}
<commit_msg>Update main.go<commit_after>package main

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"time"

	"github.com/garyburd/redigo/redis"
	"github.com/lancetw/hcfd-forecast/db"
	"github.com/lancetw/hcfd-forecast/rain"
	"github.com/line/line-bot-sdk-go/linebot"
	"github.com/robfig/cron"
)

const timeZone = "Asia/Taipei"

var bot *linebot.Client

func main() {
	c := cron.New()
	c.AddFunc("0 */3 * * * *", GoProcess)
	c.Start()

	for {
		time.Sleep(10000000000000)
		fmt.Println("sleep")
	}
}

// GoProcess is main process
func GoProcess() {
	strID := os.Getenv("ChannelID")
	numID, err := strconv.ParseInt(strID, 10, 64)
	if err != nil {
		log.Fatal("Wrong environment setting about ChannelID")
	}
	bot, err = linebot.NewClient(numID, os.Getenv("ChannelSecret"), os.Getenv("MID"))
	if err != nil {
		log.Println("Bot:", bot, " err:", err)
	}

	for {
		log.Println("=== 查詢。開始 ===")

		c := db.Connect(os.Getenv("REDISTOGO_URL"))

		targets0 := []string{"新竹市"}
		msgs0, token0 := rain.GetRainingInfo(targets0, false)

		if token0 != nil && token0 != "" {
			status0, getErr := redis.Int(c.Do("SISMEMBER", "token0", token0))
			if getErr != nil {
				log.Println(getErr)
			}

			if status0 == 0 {
				users0, smembersErr := redis.Strings(c.Do("SMEMBERS", "user"))

				if smembersErr != nil {
					log.Println("GetRainingInfo SMEMBERS redis error", smembersErr)
				} else {
					local := time.Now()
					location, timeZoneErr := time.LoadLocation(timeZone)
					if timeZoneErr == nil {
						local = local.In(location)
					}

					if len(msgs0) > 0 {
						var text string
						for _, msg := range msgs0 {
							text = text + msg + "\n\n"
						}
						for _, contentTo := range users0 {
							_, err = bot.SendText([]string{contentTo}, text)
							if err != nil {
								log.Println(err)
							}
						}
					}
				}
			}

			n0, addErr := c.Do("SADD", "token0", token0)
			if addErr != nil {
				log.Println("GetRainingInfo SADD to redis error", addErr, n0)
			}
		}

		targets1 := []string{"新竹市", "新竹縣"}
		msgs1, token1 := rain.GetWarningInfo(targets1)

		if token1 != nil && token1 != "" {
			status1, getErr := redis.Int(c.Do("SISMEMBER", "token1", token1))
			if getErr != nil {
				log.Println(getErr)
			}

			if status1 == 0 {
				users1, smembersErr := redis.Strings(c.Do("SMEMBERS", "user"))

				if smembersErr != nil {
					log.Println("GetWarningInfo SMEMBERS redis error", smembersErr)
				} else {
					local := time.Now()
					location, locationErr := time.LoadLocation(timeZone)
					if locationErr == nil {
						local = local.In(location)
					}
					for _, contentTo := range users1 {
						for _, msg := range msgs1 {
							_, msgErr := bot.SendText([]string{contentTo}, msg)
							if msgErr != nil {
								log.Println(err)
							}
						}
					}
				}
			}
		}

		if token1 != "" {
			n, addErr := c.Do("SADD", "token1", token1)
			if addErr != nil {
				log.Println("GetWarningInfo SADD to redis error", addErr, n)
			}
		}

		defer c.Close()

		log.Println("=== 查詢。結束 ===")

		time.Sleep(60 * time.Second)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"

	"github.com/scriptnull/badgeit/common"
	"github.com/scriptnull/badgeit/contracts"
	"github.com/scriptnull/badgeit/worker/downloader"
	"github.com/streadway/amqp"
)

func main() {
	log.Println("Booting Badgeit worker")

	log.Printf("Setting up connection to badgeit queue")
	username := os.Getenv("RABBIT_USERNAME")
	password := os.Getenv("RABBIT_PASSWORD")
	hostname := os.Getenv("RABBIT_HOSTNAME")
	port := os.Getenv("RABBIT_PORT")
	conStr := fmt.Sprintf("amqp://%s:%s@%s:%s/", username, password, hostname, port)
	conn, err := amqp.Dial(conStr)
	failOnError(err, "Failed to connect to RabbitMQ")
	defer conn.Close()

	ch, err := conn.Channel()
	failOnError(err, "Failed to open a channel")
	defer ch.Close()

	q, err := ch.QueueDeclare(
		"badgeit.worker", // name
		true,             // durable
		false,            // delete when unused
		false,            // exclusive
		false,            // no-wait
		nil,              // arguments
	)
	failOnError(err, "Failed to declare a queue")

	err = ch.Qos(
		1,     // prefetch count
		0,     // prefetch size
		false, // global
	)
	failOnError(err, "Failed to set QoS")

	msgs, err := ch.Consume(
		q.Name, // queue
		"",     // consumer
		false,  // auto-ack
		false,  // exclusive
		false,  // no-local
		false,  // no-wait
		nil,    // args
	)
	failOnError(err, "Failed to register a consumer")

	forever := make(chan bool)

	go func() {
		for d := range msgs {
			log.Printf("Starting Task for message: %s", d.Body)
			executeTask(d.Body)
			log.Printf("Finished Task for message: %s", d.Body)
			d.Ack(false)
		}
	}()

	log.Printf("Booted Badgeit Worker. To exit press CTRL+C")
	<-forever
}

func failOnError(err error, msg string) {
	if err != nil {
		log.Fatalf("%s: %s", msg, err)
		panic(fmt.Sprintf("%s: %s", msg, err))
	}
}

type taskResult struct {
	CallbackURL string
	// TODO: add callback headers
	Badges []common.Badge
	Error  string
}

func executeTask(message []byte) {

	// Parse input message
	payload := struct {
		Remote   string
		Download string
		Callback string
	}{}
	err := json.Unmarshal(message, &payload)
	if err != nil {
		log.Printf("Error Parsing the payload %d", err)
		return
	}

	// Create temporary directory for download operation
	dir, err := ioutil.TempDir("", "repo")
	if err != nil {
		log.Fatalln("Error creating temporary folder: ", err)
	}
	defer os.RemoveAll(dir)

	// Initiate taskResult for reporting back to the callback server\
	callbackResponse := taskResult{
		CallbackURL: payload.Callback,
	}

	// Download the repository
	d := downloader.NewDownloader(downloader.DownloaderOptions{
		Type:   payload.Download,
		Remote: payload.Remote,
		Path:   dir,
	})
	log.Println("Downloading the repository: ", payload.Remote)
	err = d.Download()
	if err != nil {
		errorStr := fmt.Sprintln("Error Downloading repository: ", err)
		callbackResponse.Error = errorStr
		callback(callbackResponse)
		return
	}
	log.Println("Downloading complete @ ", dir)

	callbackResponse.Badges = contracts.PossibleBadges(dir)
	log.Printf("Detected %d possible badges \n", len(callbackResponse.Badges))

	err = callback(callbackResponse)
	if err != nil {
		log.Println("Error While Posting callback: ", err)
	}
}

func callback(result taskResult) error {
	if result.Error == "" {
		log.Print(result.Error)
	}
	jsonPayload, err := json.Marshal(map[string]interface{}{
		"badges": result.Badges,
		"error":  result.Error,
	})
	if err != nil {
		return err
	}
	_, err = http.Post(result.CallbackURL, "application/json", strings.NewReader(string(jsonPayload)))
	if err != nil {
		return err
	}
	return nil
}
<commit_msg>Adds remote to callback response<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"

	"github.com/scriptnull/badgeit/common"
	"github.com/scriptnull/badgeit/contracts"
	"github.com/scriptnull/badgeit/worker/downloader"
	"github.com/streadway/amqp"
)

func main() {
	log.Println("Booting Badgeit worker")

	log.Printf("Setting up connection to badgeit queue")
	username := os.Getenv("RABBIT_USERNAME")
	password := os.Getenv("RABBIT_PASSWORD")
	hostname := os.Getenv("RABBIT_HOSTNAME")
	port := os.Getenv("RABBIT_PORT")
	conStr := fmt.Sprintf("amqp://%s:%s@%s:%s/", username, password, hostname, port)
	conn, err := amqp.Dial(conStr)
	failOnError(err, "Failed to connect to RabbitMQ")
	defer conn.Close()

	ch, err := conn.Channel()
	failOnError(err, "Failed to open a channel")
	defer ch.Close()

	q, err := ch.QueueDeclare(
		"badgeit.worker", // name
		true,             // durable
		false,            // delete when unused
		false,            // exclusive
		false,            // no-wait
		nil,              // arguments
	)
	failOnError(err, "Failed to declare a queue")

	err = ch.Qos(
		1,     // prefetch count
		0,     // prefetch size
		false, // global
	)
	failOnError(err, "Failed to set QoS")

	msgs, err := ch.Consume(
		q.Name, // queue
		"",     // consumer
		false,  // auto-ack
		false,  // exclusive
		false,  // no-local
		false,  // no-wait
		nil,    // args
	)
	failOnError(err, "Failed to register a consumer")

	forever := make(chan bool)

	go func() {
		for d := range msgs {
			log.Printf("Starting Task for message: %s", d.Body)
			executeTask(d.Body)
			log.Printf("Finished Task for message: %s", d.Body)
			d.Ack(false)
		}
	}()

	log.Printf("Booted Badgeit Worker. To exit press CTRL+C")
	<-forever
}

func failOnError(err error, msg string) {
	if err != nil {
		log.Fatalf("%s: %s", msg, err)
		panic(fmt.Sprintf("%s: %s", msg, err))
	}
}

type taskResult struct {
	CallbackURL string
	RemoteURL   string
	// TODO: add callback headers
	Badges []common.Badge
	Error  string
}

func executeTask(message []byte) {

	// Parse input message
	payload := struct {
		Remote   string
		Download string
		Callback string
	}{}
	err := json.Unmarshal(message, &payload)
	if err != nil {
		log.Printf("Error Parsing the payload %d", err)
		return
	}

	// Create temporary directory for download operation
	dir, err := ioutil.TempDir("", "repo")
	if err != nil {
		log.Fatalln("Error creating temporary folder: ", err)
	}
	defer os.RemoveAll(dir)

	// Initiate taskResult for reporting back to the callback server\
	callbackResponse := taskResult{
		CallbackURL: payload.Callback,
		RemoteURL:   payload.Remote,
	}

	// Download the repository
	d := downloader.NewDownloader(downloader.DownloaderOptions{
		Type:   payload.Download,
		Remote: payload.Remote,
		Path:   dir,
	})
	log.Println("Downloading the repository: ", payload.Remote)
	err = d.Download()
	if err != nil {
		errorStr := fmt.Sprintln("Error Downloading repository: ", err)
		callbackResponse.Error = errorStr
		callback(callbackResponse)
		return
	}
	log.Println("Downloading complete @ ", dir)

	callbackResponse.Badges = contracts.PossibleBadges(dir)
	log.Printf("Detected %d possible badges \n", len(callbackResponse.Badges))

	err = callback(callbackResponse)
	if err != nil {
		log.Println("Error While Posting callback: ", err)
	}
}

func callback(result taskResult) error {
	if result.Error == "" {
		log.Print(result.Error)
	}
	jsonPayload, err := json.Marshal(map[string]interface{}{
		"badges": result.Badges,
		"error":  result.Error,
		"remote": result.RemoteURL,
	})
	if err != nil {
		return err
	}
	_, err = http.Post(result.CallbackURL, "application/json", strings.NewReader(string(jsonPayload)))
	if err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package server

import (
	"errors"
	"fmt"
	"warcluster/db_manager"
	"warcluster/entities"
)

// The three constants bellow are used by calculateCanvasSize to determine the size of the area for wich information will be sent to the user.
const BEST_PING = 150
const WORST_PING = 1500
const STEPS = 10

// calculateCanvasSize is used to determine how big of an area(information about an area)
// do we need to send to the user to eleminate traces of lag.
func calculateCanvasSize(position []int, resolution []int, lag int) ([]int, []int) {
	step := int(WORST_PING - BEST_PING/STEPS)
	multiply := 1.1 + float32((lag-BEST_PING)/step)*0.1
	end_resolution := []int{
		int(float32(resolution[0]) * multiply),
		int(float32(resolution[1]) * multiply),
	}

	top_left := []int{
		position[0] - int((end_resolution[0]-resolution[0])/2),
		position[1] - int((end_resolution[1]-resolution[1])/2),
	}

	bottom_right := []int{
		position[0] + resolution[0] + int((end_resolution[0]-resolution[0])/2),
		position[1] + resolution[1] + int((end_resolution[1]-resolution[1])/2),
	}
	return top_left, bottom_right
}

// scopeOfView is not finished yet but the purpose of the function is to call calculateCanvasSize
// and give the player the information contained in the given borders.
//
//  TODO: Make some proper JSON Unmarshaling out here
func scopeOfView(request *Request) error {
	var line_missions string
	var line_planets string
	var line_suns string
	var line string
	entity_list := db_manager.GetEntities("*")
	for _, entity := range entity_list {
		switch t := entity.(type) {
		case *entities.Mission:
			if key, json, err := t.Serialize(); err == nil {
				line_missions += fmt.Sprintf("\"%v\": %s, ", key, json)
			}
		case *entities.Planet:
			if key, json, err := t.Serialize(); err == nil {
				line_planets += fmt.Sprintf("\"%v\": %s, ", key, json)
			}
		case *entities.Sun:
			if key, json, err := t.Serialize(); err == nil {
				line_suns += fmt.Sprintf("\"%v\": %s, ", key, json)
			}
		}
	}
	if len(line_missions) > 0 {
		line_missions = line_missions[:len(line_missions)-2]
	}

	line = fmt.Sprintf("{\"Command\": \"scope_of_view_result\", \"Planets\": {%v}, \"Suns\": {%v}, \"Missions\": {%v}}",
		line_planets[:len(line_planets)-2],
		line_suns[:len(line_suns)-2],
		line_missions)
	request.Client.Session.Send([]byte(fmt.Sprintf("%v", line)))
	return nil
}

// This function makes all the checks needed for creation of a new mission.
func parseAction(request *Request) error {
	var err error = nil

	defer func() error {
		if panicked := recover(); panicked != nil {
			err = errors.New("Invalid action!")
		}
		return nil
	}()

	source, err := db_manager.GetEntity(request.StartPlanet)
	if err != nil {
		return errors.New("Start planet does not exist")
	}

	target, err := db_manager.GetEntity(request.EndPlanet)
	if err != nil {
		return errors.New("End planet does not exist")
	}

	if source.(*entities.Planet).Owner != request.Client.Player.String() {
		err = errors.New("This is not your home!")
	}

	if request.Type != "Attack" || request.Type != "Supply" || request.Type != "Spy" {
		err = errors.New("Invalid mission type!")
	}

	mission := request.Client.Player.StartMission(source.(*entities.Planet), target.(*entities.Planet), request.Fleet, request.Type)
	if _, serialized_mission, err := mission.Serialize(); err == nil {
		go StartMissionary(mission)
		db_manager.SetEntity(mission)
		sessions.Broadcast([]byte(fmt.Sprintf("{ \"Command\": \"send_mission\", \"Mission\": %s}", serialized_mission)))
		if source_key, source_json, source_err := source.Serialize(); source_err == nil {
			sessions.Broadcast([]byte(fmt.Sprintf("{\"Command\": \"state_change\", \"Planets\": {\"%s\": %s}}", source_key, source_json)))
			db_manager.SetEntity(source)
		}
		return nil
	}

	return err
}
<commit_msg>Prettify the consts a little<commit_after>package server

import (
	"errors"
	"fmt"
	"warcluster/db_manager"
	"warcluster/entities"
)

// The three constants bellow are used by calculateCanvasSize to determine
//the size of the area for wich information will be sent to the user.
const (
	BEST_PING = 150
	WORST_PING = 1500
	STEPS = 10
)

// calculateCanvasSize is used to determine how big of an area(information about an area)
// do we need to send to the user to eleminate traces of lag.
func calculateCanvasSize(position []int, resolution []int, lag int) ([]int, []int) {
	step := int(WORST_PING - BEST_PING/STEPS)
	multiply := 1.1 + float32((lag-BEST_PING)/step)*0.1
	end_resolution := []int{
		int(float32(resolution[0]) * multiply),
		int(float32(resolution[1]) * multiply),
	}

	top_left := []int{
		position[0] - int((end_resolution[0]-resolution[0])/2),
		position[1] - int((end_resolution[1]-resolution[1])/2),
	}

	bottom_right := []int{
		position[0] + resolution[0] + int((end_resolution[0]-resolution[0])/2),
		position[1] + resolution[1] + int((end_resolution[1]-resolution[1])/2),
	}
	return top_left, bottom_right
}

// scopeOfView is not finished yet but the purpose of the function is to call calculateCanvasSize
// and give the player the information contained in the given borders.
//
//  TODO: Make some proper JSON Unmarshaling out here
func scopeOfView(request *Request) error {
	var line_missions string
	var line_planets string
	var line_suns string
	var line string
	entity_list := db_manager.GetEntities("*")
	for _, entity := range entity_list {
		switch t := entity.(type) {
		case *entities.Mission:
			if key, json, err := t.Serialize(); err == nil {
				line_missions += fmt.Sprintf("\"%v\": %s, ", key, json)
			}
		case *entities.Planet:
			if key, json, err := t.Serialize(); err == nil {
				line_planets += fmt.Sprintf("\"%v\": %s, ", key, json)
			}
		case *entities.Sun:
			if key, json, err := t.Serialize(); err == nil {
				line_suns += fmt.Sprintf("\"%v\": %s, ", key, json)
			}
		}
	}
	if len(line_missions) > 0 {
		line_missions = line_missions[:len(line_missions)-2]
	}

	line = fmt.Sprintf("{\"Command\": \"scope_of_view_result\", \"Planets\": {%v}, \"Suns\": {%v}, \"Missions\": {%v}}",
		line_planets[:len(line_planets)-2],
		line_suns[:len(line_suns)-2],
		line_missions)
	request.Client.Session.Send([]byte(fmt.Sprintf("%v", line)))
	return nil
}

// This function makes all the checks needed for creation of a new mission.
func parseAction(request *Request) error {
	var err error = nil

	defer func() error {
		if panicked := recover(); panicked != nil {
			err = errors.New("Invalid action!")
		}
		return nil
	}()

	source, err := db_manager.GetEntity(request.StartPlanet)
	if err != nil {
		return errors.New("Start planet does not exist")
	}

	target, err := db_manager.GetEntity(request.EndPlanet)
	if err != nil {
		return errors.New("End planet does not exist")
	}

	if source.(*entities.Planet).Owner != request.Client.Player.String() {
		err = errors.New("This is not your home!")
	}

	if request.Type != "Attack" || request.Type != "Supply" || request.Type != "Spy" {
		err = errors.New("Invalid mission type!")
	}

	mission := request.Client.Player.StartMission(source.(*entities.Planet), target.(*entities.Planet), request.Fleet, request.Type)
	if _, serialized_mission, err := mission.Serialize(); err == nil {
		go StartMissionary(mission)
		db_manager.SetEntity(mission)
		sessions.Broadcast([]byte(fmt.Sprintf("{ \"Command\": \"send_mission\", \"Mission\": %s}", serialized_mission)))
		if source_key, source_json, source_err := source.Serialize(); source_err == nil {
			sessions.Broadcast([]byte(fmt.Sprintf("{\"Command\": \"state_change\", \"Planets\": {\"%s\": %s}}", source_key, source_json)))
			db_manager.SetEntity(source)
		}
		return nil
	}

	return err
}
<|endoftext|>


<commit_before>package fuseshim

import (
	"bytes"
	"errors"
	"os/exec"
)

func unmount(dir string) error {
	cmd := exec.Command("fusermount", "-u", dir)
	output, err := cmd.CombinedOutput()
	if err != nil {
		if len(output) > 0 {
			output = bytes.TrimRight(output, "\n")
			msg := err.Error() + ": " + string(output)
			err = errors.New(msg)
		}
		return err
	}
	return nil
}
<commit_msg>Fixed unmount_linux.go.<commit_after>package fuseshim

import (
	"bytes"
	"fmt"
	"os/exec"
)

func unmount(dir string) (err error) {
	// Call fusermount.
	cmd := exec.Command("fusermount", "-u", dir)
	output, err := cmd.CombinedOutput()
	if err != nil {
		if len(output) > 0 {
			output = bytes.TrimRight(output, "\n")
			err = fmt.Errorf("%v: %s", err, output)
		}

		return
	}

	return
}
<|endoftext|>