<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.

// Helper code for daemonizing gcsfuse, synchronizing on successful mount.
//
// The details of this package are subject to change.
package daemon

import (
	"encoding/gob"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"strconv"
	"syscall"
)

// The name of an environment variable used to communicate a file descriptor
// set up by Mount to the gcsfuse subprocess. Gob encoding is used to
// communicate back to Mount.
const envVar = "MOUNT_STATUS_FD"

// A message containing logging output for the process of mounting the file
// system.
type logMsg struct {
	Msg []byte
}

// A message indicating the outcome of the process of mounting the file system.
// The receiver ignores further messages.
type outcomeMsg struct {
	Successful bool

	// Meaningful only if !Successful.
	ErrorMsg string
}

func init() {
	gob.Register(logMsg{})
	gob.Register(outcomeMsg{})
}

// The file provded to this process via the environment variable, or nil if
// none.
var gFile *os.File

// A gob encoder that writes into gFile, or nil.
var gGobEncoder *gob.Encoder

func init() {
	// Is the environment variable set?
	fdStr, ok := os.LookupEnv(envVar)
	if !ok {
		return
	}

	// Parse the file descriptor.
	fd, err := strconv.ParseUint(fdStr, 10, 32)
	if err != nil {
		log.Fatalf("Couldn't parse %s value %q: %v", envVar, fdStr, err)
	}

	// Set up the file and the encoder that wraps it.
	gFile = os.NewFile(uintptr(fd), envVar)
	gGobEncoder = gob.NewEncoder(gFile)
}

// Send the supplied message as an interface{}, matching the decoder.
func sendMsg(msg interface{}) (err error) {
	err = gGobEncoder.Encode(&msg)
	return
}

// For use by gcsfuse: signal that mounting was successful (allowing the caller
// of the process to return in success) or that there was a failure to mount
// the file system (allowing the caller of the process to display an
// appropriate error message).
//
// Do nothing if the process wasn't invoked with Mount.
func SignalOutcome(outcome error) (err error) {
	// Is there anything to do?
	if gGobEncoder == nil {
		return
	}

	// Write out the outcome.
	msg := &outcomeMsg{
		Successful: outcome == nil,
	}

	if !msg.Successful {
		msg.ErrorMsg = outcome.Error()
	}

	err = sendMsg(msg)

	return
}

// An io.Writer that sends logMsg messages over gGobEncoder.
type logMsgWriter struct {
}

func (w *logMsgWriter) Write(p []byte) (n int, err error) {
	msg := &logMsg{
		Msg: p,
	}

	err = sendMsg(msg)
	if err != nil {
		return
	}

	n = len(p)
	return
}

// For use by gcsfuse: return a writer that should be used for logging status
// messages while in the process of mounting.
//
// The returned writer must not be written to after calling SignalOutcome.
func StatusWriter() (w io.Writer) {
	if gGobEncoder == nil {
		w = os.Stderr
		return
	}

	w = &logMsgWriter{}
	return
}

// Invoke gcsfuse with the supplied arguments, waiting until it successfully
// mounts or reports that is has failed. Write status updates while mounting
// into the supplied writer (which may be nil for silence). Return nil only if
// it mounts successfully.
func Mount(
	gcsfusePath string,
	fusermountPath string,
	args []string,
	status io.Writer) (err error) {
	if status == nil {
		status = ioutil.Discard
	}

	// Set up the pipe that we will hand to the gcsfuse subprocess.
	pipeR, pipeW, err := os.Pipe()
	if err != nil {
		err = fmt.Errorf("Pipe: %v", err)
		return
	}

	// Attempt to start gcsfuse. If we encounter an error in so doing, write it
	// to the channel.
	startGcsfuseErr := make(chan error, 1)
	go func() {
		defer pipeW.Close()
		err := startGcsfuse(gcsfusePath, fusermountPath, args, pipeW)
		if err != nil {
			startGcsfuseErr <- err
		}
	}()

	// Read communication from gcsfuse from the pipe, writing nil into the
	// channel only if the mount succeeds.
	readFromGcsfuseOutcome := make(chan error, 1)
	go func() {
		defer pipeR.Close()
		readFromGcsfuseOutcome <- readFromGcsfuse(pipeR, status)
	}()

	// Wait for a result from one of the above.
	select {
	case err = <-startGcsfuseErr:
		err = fmt.Errorf("startGcsfuse: %v", err)
		return

	case err = <-readFromGcsfuseOutcome:
		if err == nil {
			// All is good.
			return
		}

		err = fmt.Errorf("readFromGcsfuse: %v", err)
		return
	}
}

// Start gcsfuse, handing it the supplied pipe for communication. Do not wait
// for it to return.
func startGcsfuse(
	gcsfusePath string,
	fusermountPath string,
	args []string,
	pipeW *os.File) (err error) {
	cmd := exec.Command(gcsfusePath)
	cmd.Args = append(cmd.Args, args...)
	cmd.ExtraFiles = []*os.File{pipeW}

	// Change working directories so that we don't prevent unmounting of the
	// volume of our current working directory.
	cmd.Dir = "/"

	// Call setsid after forking in order to avoid being killed when the user
	// logs out.
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Setsid: true,
	}

	// Send along the write end of the pipe, and let gcsfuse find fusermount.
	cmd.Env = []string{
		fmt.Sprintf("%s=3", envVar),
		fmt.Sprintf("PATH=%s", path.Dir(fusermountPath)),
	}

	// Start. Clean up in the background, ignoring errors.
	err = cmd.Start()
	go cmd.Wait()

	return
}

// Process communication from a gcsfuse subprocess. Write log messages to the
// supplied writer (which must be non-nil). Return nil only if the output of
// mounting is success.
func readFromGcsfuse(
	r io.Reader,
	status io.Writer) (err error) {
	decoder := gob.NewDecoder(r)

	for {
		// Read a message.
		var msg interface{}
		err = decoder.Decode(&msg)
		if err != nil {
			err = fmt.Errorf("Decode: %v", err)
			return
		}

		// Handle the message.
		switch msg := msg.(type) {
		case logMsg:
			_, err = status.Write(msg.Msg)
			if err != nil {
				err = fmt.Errorf("status.Write: %v", err)
				return
			}

		case outcomeMsg:
			if msg.Successful {
				return
			}

			err = fmt.Errorf("gcsfuse: %s", msg.ErrorMsg)
			return

		default:
			err = fmt.Errorf("Unhandled message type: %T", msg)
			return
		}
	}
}
<commit_msg>Update the package overview.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.

// Helper code for starting a daemon process.
//
// This package assumes that the user invokes a tool, which invokes a daemon
// process. Though the tool starts the daemon process with stdin, stdout, and
// stderr closed (using Run), the daemon should be able to communicate status
// to the user while it starts up (using StatusWriter), causing the tool to
// exit in success or failure only when it is clear whether the daemon has
// sucessfully started (which it signal using SignalOutcome).
package daemonize

import (
	"encoding/gob"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"strconv"
	"syscall"
)

// The name of an environment variable used to communicate a file descriptor
// set up by Mount to the gcsfuse subprocess. Gob encoding is used to
// communicate back to Mount.
const envVar = "MOUNT_STATUS_FD"

// A message containing logging output for the process of mounting the file
// system.
type logMsg struct {
	Msg []byte
}

// A message indicating the outcome of the process of mounting the file system.
// The receiver ignores further messages.
type outcomeMsg struct {
	Successful bool

	// Meaningful only if !Successful.
	ErrorMsg string
}

func init() {
	gob.Register(logMsg{})
	gob.Register(outcomeMsg{})
}

// The file provded to this process via the environment variable, or nil if
// none.
var gFile *os.File

// A gob encoder that writes into gFile, or nil.
var gGobEncoder *gob.Encoder

func init() {
	// Is the environment variable set?
	fdStr, ok := os.LookupEnv(envVar)
	if !ok {
		return
	}

	// Parse the file descriptor.
	fd, err := strconv.ParseUint(fdStr, 10, 32)
	if err != nil {
		log.Fatalf("Couldn't parse %s value %q: %v", envVar, fdStr, err)
	}

	// Set up the file and the encoder that wraps it.
	gFile = os.NewFile(uintptr(fd), envVar)
	gGobEncoder = gob.NewEncoder(gFile)
}

// Send the supplied message as an interface{}, matching the decoder.
func sendMsg(msg interface{}) (err error) {
	err = gGobEncoder.Encode(&msg)
	return
}

// For use by gcsfuse: signal that mounting was successful (allowing the caller
// of the process to return in success) or that there was a failure to mount
// the file system (allowing the caller of the process to display an
// appropriate error message).
//
// Do nothing if the process wasn't invoked with Mount.
func SignalOutcome(outcome error) (err error) {
	// Is there anything to do?
	if gGobEncoder == nil {
		return
	}

	// Write out the outcome.
	msg := &outcomeMsg{
		Successful: outcome == nil,
	}

	if !msg.Successful {
		msg.ErrorMsg = outcome.Error()
	}

	err = sendMsg(msg)

	return
}

// An io.Writer that sends logMsg messages over gGobEncoder.
type logMsgWriter struct {
}

func (w *logMsgWriter) Write(p []byte) (n int, err error) {
	msg := &logMsg{
		Msg: p,
	}

	err = sendMsg(msg)
	if err != nil {
		return
	}

	n = len(p)
	return
}

// For use by gcsfuse: return a writer that should be used for logging status
// messages while in the process of mounting.
//
// The returned writer must not be written to after calling SignalOutcome.
func StatusWriter() (w io.Writer) {
	if gGobEncoder == nil {
		w = os.Stderr
		return
	}

	w = &logMsgWriter{}
	return
}

// Invoke gcsfuse with the supplied arguments, waiting until it successfully
// mounts or reports that is has failed. Write status updates while mounting
// into the supplied writer (which may be nil for silence). Return nil only if
// it mounts successfully.
func Mount(
	gcsfusePath string,
	fusermountPath string,
	args []string,
	status io.Writer) (err error) {
	if status == nil {
		status = ioutil.Discard
	}

	// Set up the pipe that we will hand to the gcsfuse subprocess.
	pipeR, pipeW, err := os.Pipe()
	if err != nil {
		err = fmt.Errorf("Pipe: %v", err)
		return
	}

	// Attempt to start gcsfuse. If we encounter an error in so doing, write it
	// to the channel.
	startGcsfuseErr := make(chan error, 1)
	go func() {
		defer pipeW.Close()
		err := startGcsfuse(gcsfusePath, fusermountPath, args, pipeW)
		if err != nil {
			startGcsfuseErr <- err
		}
	}()

	// Read communication from gcsfuse from the pipe, writing nil into the
	// channel only if the mount succeeds.
	readFromGcsfuseOutcome := make(chan error, 1)
	go func() {
		defer pipeR.Close()
		readFromGcsfuseOutcome <- readFromGcsfuse(pipeR, status)
	}()

	// Wait for a result from one of the above.
	select {
	case err = <-startGcsfuseErr:
		err = fmt.Errorf("startGcsfuse: %v", err)
		return

	case err = <-readFromGcsfuseOutcome:
		if err == nil {
			// All is good.
			return
		}

		err = fmt.Errorf("readFromGcsfuse: %v", err)
		return
	}
}

// Start gcsfuse, handing it the supplied pipe for communication. Do not wait
// for it to return.
func startGcsfuse(
	gcsfusePath string,
	fusermountPath string,
	args []string,
	pipeW *os.File) (err error) {
	cmd := exec.Command(gcsfusePath)
	cmd.Args = append(cmd.Args, args...)
	cmd.ExtraFiles = []*os.File{pipeW}

	// Change working directories so that we don't prevent unmounting of the
	// volume of our current working directory.
	cmd.Dir = "/"

	// Call setsid after forking in order to avoid being killed when the user
	// logs out.
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Setsid: true,
	}

	// Send along the write end of the pipe, and let gcsfuse find fusermount.
	cmd.Env = []string{
		fmt.Sprintf("%s=3", envVar),
		fmt.Sprintf("PATH=%s", path.Dir(fusermountPath)),
	}

	// Start. Clean up in the background, ignoring errors.
	err = cmd.Start()
	go cmd.Wait()

	return
}

// Process communication from a gcsfuse subprocess. Write log messages to the
// supplied writer (which must be non-nil). Return nil only if the output of
// mounting is success.
func readFromGcsfuse(
	r io.Reader,
	status io.Writer) (err error) {
	decoder := gob.NewDecoder(r)

	for {
		// Read a message.
		var msg interface{}
		err = decoder.Decode(&msg)
		if err != nil {
			err = fmt.Errorf("Decode: %v", err)
			return
		}

		// Handle the message.
		switch msg := msg.(type) {
		case logMsg:
			_, err = status.Write(msg.Msg)
			if err != nil {
				err = fmt.Errorf("status.Write: %v", err)
				return
			}

		case outcomeMsg:
			if msg.Successful {
				return
			}

			err = fmt.Errorf("gcsfuse: %s", msg.ErrorMsg)
			return

		default:
			err = fmt.Errorf("Unhandled message type: %T", msg)
			return
		}
	}
}
<|endoftext|>


<commit_before>package google

import (
	"errors"
	"fmt"
	"log"

	"github.com/hashicorp/terraform/helper/schema"

	"google.golang.org/api/storage/v1"
)

func resourceStorageBucket() *schema.Resource {
	return &schema.Resource{
		Create: resourceStorageBucketCreate,
		Read:   resourceStorageBucketRead,
		Update: resourceStorageBucketUpdate,
		Delete: resourceStorageBucketDelete,

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"predefined_acl": &schema.Schema{
				Type:     schema.TypeString,
				Default:  "projectPrivate",
				Optional: true,
				ForceNew: true,
			},
			"location": &schema.Schema{
				Type:     schema.TypeString,
				Default:  "US",
				Optional: true,
				ForceNew: true,
			},
			"force_destroy": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},
		},
	}
}

func resourceStorageBucketCreate(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	// Get the bucket and acl
	bucket := d.Get("name").(string)
	acl := d.Get("predefined_acl").(string)
	location := d.Get("location").(string)

	// Create a bucket, setting the acl, location and name.
	sb := &storage.Bucket{Name: bucket, Location: location}
	res, err := config.clientStorage.Buckets.Insert(config.Project, sb).PredefinedAcl(acl).Do()

	if err != nil {
		fmt.Printf("Error creating bucket %s: %v", bucket, err)
		return err
	}

	log.Printf("[DEBUG] Created bucket %v at location %v\n\n", res.Name, res.SelfLink)

	// Assign the bucket ID as the resource ID
	d.SetId(res.Id)

	return nil
}

func resourceStorageBucketUpdate(d *schema.ResourceData, meta interface{}) error {
	// Only thing you can currently change is force_delete (all other properties have ForceNew)
	// which is just terraform object state change, so nothing to do here
	return nil
}

func resourceStorageBucketRead(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	// Get the bucket and acl
	bucket := d.Get("name").(string)
	res, err := config.clientStorage.Buckets.Get(bucket).Do()

	if err != nil {
		fmt.Printf("Error reading bucket %s: %v", bucket, err)
		return err
	}

	log.Printf("[DEBUG] Read bucket %v at location %v\n\n", res.Name, res.SelfLink)

	// Update the bucket ID according to the resource ID
	d.SetId(res.Id)

	return nil
}

func resourceStorageBucketDelete(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	// Get the bucket
	bucket := d.Get("name").(string)

	for {
		res, err := config.clientStorage.Objects.List(bucket).Do()
		if err != nil {
			fmt.Printf("Error Objects.List failed: %v", err)
			return err
		}

		if len(res.Items) != 0 {
			if d.Get("force_destroy").(bool) {
				// purge the bucket...
				log.Printf("[DEBUG] GCS Bucket attempting to forceDestroy\n\n")

				for _, object := range res.Items {
					log.Printf("[DEBUG] Found %s", object.Name)
					if err := config.clientStorage.Objects.Delete(bucket, object.Name).Do(); err != nil {
						log.Fatalf("Error trying to delete object: %s %s\n\n", object.Name, err)
					} else {
						log.Printf("Object deleted: %s \n\n", object.Name)
					}
				}

			} else {
				delete_err := errors.New("Error trying to delete a bucket containing objects without `force_destroy` set to true")
				log.Printf("Error! %s : %s\n\n", bucket, delete_err)
				return delete_err
			}
		} else {
			break // 0 items, bucket empty
		}
	}

	// remove empty bucket
	err := config.clientStorage.Buckets.Delete(bucket).Do()
	if err != nil {
		fmt.Printf("Error deleting bucket %s: %v\n\n", bucket, err)
		return err
	}
	log.Printf("[DEBUG] Deleted bucket %v\n\n", bucket)

	return nil
}
<commit_msg>Address issue  #2292<commit_after>package google

import (
	"errors"
	"fmt"
	"log"

	"github.com/hashicorp/terraform/helper/schema"

	"google.golang.org/api/storage/v1"
)

func resourceStorageBucket() *schema.Resource {
	return &schema.Resource{
		Create: resourceStorageBucketCreate,
		Read:   resourceStorageBucketRead,
		Update: resourceStorageBucketUpdate,
		Delete: resourceStorageBucketDelete,

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"predefined_acl": &schema.Schema{
				Type:     schema.TypeString,
				Default:  "projectPrivate",
				Optional: true,
				ForceNew: true,
			},
			"location": &schema.Schema{
				Type:     schema.TypeString,
				Default:  "US",
				Optional: true,
				ForceNew: true,
			},
			"force_destroy": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},
			"website": &schema.Schema{
				Type:     schema.TypeList,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"main_page_suffix": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
						},
						"not_found_page": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
						},
					},
				},
			},
			"self_link": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

func resourceStorageBucketCreate(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	// Get the bucket and acl
	bucket := d.Get("name").(string)
	acl := d.Get("predefined_acl").(string)
	location := d.Get("location").(string)

	// Create a bucket, setting the acl, location and name.
	sb := &storage.Bucket{Name: bucket, Location: location}

	if v, ok := d.GetOk("website"); ok {
		websites := v.([]interface{})

		if len(websites) > 1 {
			return fmt.Errorf("At most one website block is allowed")
		}

		sb.Website = &storage.BucketWebsite{}

		website := websites[0].(map[string]interface{})

		if v, ok := website["not_found_page"]; ok {
			sb.Website.NotFoundPage = v.(string)
		}

		if v, ok := website["main_page_suffix"]; ok {
			sb.Website.MainPageSuffix = v.(string)
		}
	}

	res, err := config.clientStorage.Buckets.Insert(config.Project, sb).PredefinedAcl(acl).Do()

	if err != nil {
		fmt.Printf("Error creating bucket %s: %v", bucket, err)
		return err
	}

	log.Printf("[DEBUG] Created bucket %v at location %v\n\n", res.Name, res.SelfLink)

	// Assign the bucket ID as the resource ID
	d.Set("self_link", res.SelfLink)
	d.SetId(res.Id)

	return nil
}

func resourceStorageBucketUpdate(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	sb := &storage.Bucket{}

	if d.HasChange("website") {
		if v, ok := d.GetOk("website"); ok {
			websites := v.([]interface{})

			if len(websites) > 1 {
				return fmt.Errorf("At most one website block is allowed")
			}

			// Setting fields to "" to be explicit that the PATCH call will
			// delete this field.
			if len(websites) == 0 {
				sb.Website.NotFoundPage = ""
				sb.Website.MainPageSuffix = ""
			} else {
				website := websites[0].(map[string]interface{})
				sb.Website = &storage.BucketWebsite{}
				if v, ok := website["not_found_page"]; ok {
					sb.Website.NotFoundPage = v.(string)
				} else {
					sb.Website.NotFoundPage = ""
				}

				if v, ok := website["main_page_suffix"]; ok {
					sb.Website.MainPageSuffix = v.(string)
				} else {
					sb.Website.MainPageSuffix = ""
				}
			}
		}
	}

	res, err := config.clientStorage.Buckets.Patch(d.Get("name").(string), sb).Do()

	if err != nil {
		return err
	}

	log.Printf("[DEBUG] Patched bucket %v at location %v\n\n", res.Name, res.SelfLink)

	// Assign the bucket ID as the resource ID
	d.Set("self_link", res.SelfLink)
	d.SetId(res.Id)

	return nil
}

func resourceStorageBucketRead(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	// Get the bucket and acl
	bucket := d.Get("name").(string)
	res, err := config.clientStorage.Buckets.Get(bucket).Do()

	if err != nil {
		fmt.Printf("Error reading bucket %s: %v", bucket, err)
		return err
	}

	log.Printf("[DEBUG] Read bucket %v at location %v\n\n", res.Name, res.SelfLink)

	// Update the bucket ID according to the resource ID
	d.Set("self_link", res.SelfLink)
	d.SetId(res.Id)

	return nil
}

func resourceStorageBucketDelete(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	// Get the bucket
	bucket := d.Get("name").(string)

	for {
		res, err := config.clientStorage.Objects.List(bucket).Do()
		if err != nil {
			fmt.Printf("Error Objects.List failed: %v", err)
			return err
		}

		if len(res.Items) != 0 {
			if d.Get("force_destroy").(bool) {
				// purge the bucket...
				log.Printf("[DEBUG] GCS Bucket attempting to forceDestroy\n\n")

				for _, object := range res.Items {
					log.Printf("[DEBUG] Found %s", object.Name)
					if err := config.clientStorage.Objects.Delete(bucket, object.Name).Do(); err != nil {
						log.Fatalf("Error trying to delete object: %s %s\n\n", object.Name, err)
					} else {
						log.Printf("Object deleted: %s \n\n", object.Name)
					}
				}

			} else {
				delete_err := errors.New("Error trying to delete a bucket containing objects without `force_destroy` set to true")
				log.Printf("Error! %s : %s\n\n", bucket, delete_err)
				return delete_err
			}
		} else {
			break // 0 items, bucket empty
		}
	}

	// remove empty bucket
	err := config.clientStorage.Buckets.Delete(bucket).Do()
	if err != nil {
		fmt.Printf("Error deleting bucket %s: %v\n\n", bucket, err)
		return err
	}
	log.Printf("[DEBUG] Deleted bucket %v\n\n", bucket)

	return nil
}
<|endoftext|>


<commit_before>package charts

// 图形上的文本标签配置项
type LabelTextOpts struct {
	// 是否显示标签
	Show bool `json:"show,omitempty"`
	// 文字的颜色
	Color string `json:"color,omitempty"`
	// 标签的位置
	// 通过相对的百分比或者绝对像素值表示标签相对于图形包围盒左上角的位置。示例：
	// 绝对的像素值	position: [10, 10],
	// 相对的百分比	position: ["50%", "50%"]
	// "top", "left", "right", "bottom"
	// "inside", "insideLeft", "insideRight", "insideTop", "insideBottom"
	// "insideTopLeft", "insideBottomLeft", "insideTopRight", "insideBottomRight"
	Position string `json:"position,omitempty"`
}

// MarkLine 配置项
type MarkPointOpts struct {
	Data []interface{} `json:"data,omitempty"`
	MPStyleOpts
}

// MarkPoint 风格配置项
type MPStyleOpts struct {
	// 图元的图形类别
	// 可选 "circle", "rect", "roundRect", "triangle", "diamond", "pin", "arrow", "none"
	Symbol string `json:"symbol,omitempty"`
	// 图元的大小
	SymbolSize float32 `json:"symbolSize,omitempty"`
}

// MarkPoint 数据 Name-Type
type MPNameTypeItem struct {
	// 标记点名称
	Name string `json:"name"`
	// 内置类型，可选 "average", "min", "max"
	Type string `json:"type"`
	// 在使用 type 时有效，用于指定在哪个维度上指定最大值最小值。
	// 可以是维度的直接名称，例如折线图时可以是 x、angle 等、candlestick 图时可以是 open、close 等维度名称。
	ValueDim string `json:"valueDim,omitempty"`
}

// MarkPoint 数据 Name-Coordinates
type MPNameCoordItem struct {
	// 标记点名称
	Name string `json:"name"`
	// 标记点坐标
	Coord []interface{} `json:"coord"`
	// 在使用 type 时有效，用于指定在哪个维度上指定最大值最小值。
	// 可以是维度的直接名称，例如折线图时可以是 x、angle 等、candlestick 图时可以是 open、close 等维度名称。
	ValueDim string `json:"valueDim,omitempty"`
}

// MarkLine 配置项
type MarkLineOpts struct {
	Data []interface{} `json:"data,omitempty"`
	MLStyleOpts
}

// MarkLine 风格配置项
type MLStyleOpts struct {
	// 图元的图形类别
	// 可选 "circle", "rect", "roundRect", "triangle", "diamond", "pin", "arrow", "none"
	Symbol []string `json:"symbol,omitempty"`
	// 图元的大小
	SymbolSize float32 `json:"symbolSize,omitempty"`
}

// MarkLine 数据 Name-Type
type MLNameTypeItem struct {
	// 标记线名称
	Name string `json:"name"`
	// 内置类型，可选 "average", "min", "max"
	Type string `json:"type"`
	// 在使用 type 时有效，用于指定在哪个维度上指定最大值最小值。
	// 可以是维度的直接名称，例如折线图时可以是 x、angle 等、candlestick 图时可以是 open、close 等维度名称。
	ValueDim string `json:"valueDim,omitempty"`
}

// MarkLine 数据 Name-YAxis
type MLNameYAxisItem struct {
	// 标记线名称
	Name string `json:"name"`
	// Y 轴数据
	YAxis interface{} `json:"yAxis"`
	// 在使用 type 时有效，用于指定在哪个维度上指定最大值最小值。
	// 可以是维度的直接名称，例如折线图时可以是 x、angle 等、candlestick 图时可以是 open、close 等维度名称。
	ValueDim string `json:"valueDim,omitempty"`
}

// MarkLine 数据 Name-XAxis
type MLNameXAxisItem struct {
	// 标记线名称
	Name string `json:"name"`
	// X 轴数据
	XAxis interface{} `json:"xAxis"`
	// 在使用 type 时有效，用于指定在哪个维度上指定最大值最小值。
	// 可以是维度的直接名称，例如折线图时可以是 x、angle 等、candlestick 图时可以是 open、close 等维度名称。
	ValueDim string `json:"valueDim,omitempty"`
}

// MarkLine 数据 Name-Coordinates
type MLNameCoordItem struct {
	// 标记线名称
	Name string
	// 标记线起始坐标
	Coord0 []interface{}
	// 标记线结束坐标
	Coord1 []interface{}
	// 在使用 type 时有效，用于指定在哪个维度上指定最大值最小值。
	// 可以是维度的直接名称，例如折线图时可以是 x、angle 等、candlestick 图时可以是 open、close 等维度名称。
	ValueDim string `json:"valueDim,omitempty"`
}

type ItemStyleOpts struct {
	// 图形的颜色
	// Kline 图中为 阳线图形颜色
	Color string `json:"color,omitempty"`
	// Kline 图中为 阴线图形颜色
	Color0 string `json:"color0,omitempty"`
	// 图形的描边颜色
	// Kline 途中为 阳线图形的描边颜色
	BorderColor string `json:"borderColor,omitempty"`
	// Kline 途中为 阴线图形的描边颜色
	BorderColor0 string `json:"borderColor0,omitempty"`
	// 图形透明度。支持从 0 到 1 的数字，为 0 时不绘制该图形
	Opacity float32 `json:"opacity,omitempty"`
}

type singleSeries struct {
	// 系列名称
	Name string `json:"name,omitempty"`
	// 系列类型
	Type string `json:"type"`

	// Rectangular charts
	// 数据堆叠，同个类目轴上系列配置相同的 stack 值后，后一个系列的值会在前一个系列的值上相加
	Stack string `json:"stack,omitempty"`
	// 使用的 X 轴的 index，在单个图表实例中存在多个 X 轴的时候有用
	XAxisIndex int `json:"xAxisIndex,omitempty"`
	// 使用的 Y 轴的 index，在单个图表实例中存在多个 Y 轴的时候有用
	YAxisIndex int `json:"yAxisIndex,omitempty"`

	// Map charts
	MapType     string `json:"map,omitempty"`
	CoordSystem string `json:"coordinateSystem,omitempty"`

	// Line charts
	// 是否是阶梯线图。可以设置为 true 显示成阶梯线图。
	Step bool `json:"step,omitempty"`
	// 是否平滑曲线显示
	Smooth bool `json:"smooth,omitempty"`

	// TODO: example
	// pie charts
	// 是否展示成南丁格尔图，通过半径区分数据大小。可选择两种模式：
	// "radius": 扇区圆心角展现数据的百分比，半径展现数据的大小。
	// "area": 所有扇区圆心角相同，仅通过半径展现数据大小。
	RoseType interface{} `json:"roseType,omitempty"`
	// 饼图的中心（圆心）坐标，数组的第一项是横坐标，第二项是纵坐标。
	// 支持设置成百分比，设置成百分比时第一项是相对于容器宽度，第二项是相对于容器高度。
	// 示例
	// center: [400, 300] 设置成绝对的像素值
	// center: ["50%", "50%"] 设置成相对的百分比
	// 默认
	// ["50%", "50%"]
	Center interface{} `json:"center,omitempty"`
	// 饼图的半径。可以为如下类型：
	// number：直接指定外半径值。
	// string：例如，"20%"，表示外半径为可视区尺寸（容器高宽中较小一项）的 20% 长度。
	// Array.<number|string>：数组的第一项是内半径，第二项是外半径。每一项遵从上述 number string 的描述。
	// 默认
	// [0, "75%"]
	Radius interface{} `json:"radius,omitempty"`

	// wordCloud
	// 词云图形状
	// 可选 "circle", "cardioid", "diamond", "triangle-forward", "triangle", "pentagon", "star"
	// 默认 "circle"
	Shape string `json:"shape,omitempty"`
	// 字体大小范围
	SizeRange []float32 `json:"sizeRange,omitempty"`
	// 字体倾斜角度范围
	RotationRange []float32 `json:"rotationRange,omitempty"`

	// Liquid
	LiquidOutlineOpts `json:"outline,omitempty"`
	IsWaveAnimation   bool `json:"waveAnimation"`

	// 系列数据项
	Data interface{} `json:"data"`

	// 系列配置项
	ItemStyleOpts    `json:"itemStyle,omitempty"`
	LabelTextOpts    `json:"label,omitempty"`
	MarkLineOpts     `json:"markLine,omitempty"`
	MarkPointOpts    `json:"markPoint,omitempty"`
	RippleEffectOpts `json:"rippleEffect,omitempty"`
	LineStyleOpts    `json:"lineStyle,omitempty"`
	AreaStyleOpts    `json:"areaStyle,omitempty"`
	TextStyleOpts    `json:"textStyle,omitempty"`
}

// 设置 singleSeries 配置项
func (s *singleSeries) switchSeriesOpts(options ...interface{}) {
	// 实际 MarkLevel Name Coordinates 结构
	type MLNameCoord struct {
		Name  string        `json:"name,omitempty"`
		Coord []interface{} `json:"coord"`
	}

	for i := 0; i < len(options); i++ {
		option := options[i]
		switch option.(type) {
		case LabelTextOpts:
			s.LabelTextOpts = option.(LabelTextOpts)
		case RippleEffectOpts:
			s.RippleEffectOpts = option.(RippleEffectOpts)
		case LineStyleOpts:
			s.LineStyleOpts = option.(LineStyleOpts)
		case AreaStyleOpts:
			s.AreaStyleOpts = option.(AreaStyleOpts)
		case ItemStyleOpts:
			s.ItemStyleOpts = option.(ItemStyleOpts)
		case TextStyleOpts:
			s.TextStyleOpts = option.(TextStyleOpts)
			s.TextStyleOpts.Normal = &TextStyleOpts{
				Color:     s.TextStyleOpts.Color,
				FontSize:  s.TextStyleOpts.FontSize,
				FontStyle: s.TextStyleOpts.FontStyle,
			}

			// MarkLine 配置项
		case MLNameTypeItem:
			s.MarkLineOpts.Data = append(s.MarkLineOpts.Data, option.(MLNameTypeItem))
		case MLNameXAxisItem:
			s.MarkLineOpts.Data = append(s.MarkLineOpts.Data, option.(MLNameXAxisItem))
		case MLNameYAxisItem:
			s.MarkLineOpts.Data = append(s.MarkLineOpts.Data, option.(MLNameYAxisItem))
		case MLNameCoordItem:
			m := option.(MLNameCoordItem)
			s.MarkLineOpts.Data = append(
				s.MarkLineOpts.Data, []MLNameCoord{{Name: m.Name, Coord: m.Coord0}, {Coord: m.Coord1}})
		case MLStyleOpts:
			s.MarkLineOpts.MLStyleOpts = option.(MLStyleOpts)

			// MarkPoint 配置项
		case MPNameTypeItem:
			s.MarkPointOpts.Data = append(s.MarkPointOpts.Data, option.(MPNameTypeItem))
		case MPNameCoordItem:
			s.MarkPointOpts.Data = append(s.MarkPointOpts.Data, option.(MPNameCoordItem))
		case MPStyleOpts:
			s.MarkPointOpts.MPStyleOpts = option.(MPStyleOpts)

		case BarOpts:
			opt := option.(BarOpts)
			opt.setChartOpt(s)
		case HeatMapOpts:
			opt := option.(HeatMapOpts)
			opt.setChartOpt(s)
		case LineOpts:
			opt := option.(LineOpts)
			opt.setChartOpt(s)
		case LiquidOpts:
			opt := option.(LiquidOpts)
			opt.setChartOpt(s)
		case PieOpts:
			opt := option.(PieOpts)
			opt.setChartOpt(s)
		case WordCLoudOpts:
			opt := option.(WordCLoudOpts)
			opt.setChartOpt(s)
		}
	}
}

func (s *singleSeries) setSingleSeriesOpts(options ...interface{}) {
	s.switchSeriesOpts(options...)
}

type Series []singleSeries

type serieser interface {
	exportSeries() Series
}

func (series *Series) exportSeries() Series {
	return *series
}

func (series *Series) SetSeriesConfig(options ...interface{}) {
	tsl := *series
	for i := 0; i < len(tsl); i++ {
		tsl[i].switchSeriesOpts(options...)
	}
}
<commit_msg>Add: Markline/Markpoint 新增 label 配置项<commit_after>package charts

// 图形上的文本标签配置项
type LabelTextOpts struct {
	// 是否显示标签
	Show bool `json:"show,omitempty"`
	// 文字的颜色
	Color string `json:"color,omitempty"`
	// 标签的位置
	// 通过相对的百分比或者绝对像素值表示标签相对于图形包围盒左上角的位置。示例：
	// 绝对的像素值	position: [10, 10],
	// 相对的百分比	position: ["50%", "50%"]
	// "top", "left", "right", "bottom"
	// "inside", "insideLeft", "insideRight", "insideTop", "insideBottom"
	// "insideTopLeft", "insideBottomLeft", "insideTopRight", "insideBottomRight"
	Position string `json:"position,omitempty"`
	// 标签内容格式器，支持字符串模板和回调函数两种形式，字符串模板与回调函数返回的字符串均支持用 \n 换行。
	// 1. 字符串模板 模板变量有：
	// {a}：系列名。
	// {b}：数据名。
	// {c}：数据值。
	// {@xxx}：数据中名为'xxx'的维度的值，如{@product}表示名为'product'`的维度的值。
	// {@[n]}：数据中维度 n 的值，如{@[3]}` 表示维度 3 的值，从 0 开始计数。
	// 2, 回调函数
	// 回调函数格式：
	// (params: Object|Array, ticket: string, callback: (ticket: string, html: string)) => string
	// 第一个参数 params 是 formatter 需要的数据集。格式如下：
	// {
	//    componentType: 'series',
	//    seriesType: string,	// 系列类型
	//    seriesIndex: number,	// 系列在传入的 option.series 中的 index
	//    seriesName: string,	// 系列名称
	//    name: string,			// 数据名，类目名
	//    dataIndex: number,	// 数据在传入的 data 数组中的 index
	//    data: Object,			// 传入的原始数据项
	//    value: number|Array,	// 传入的数据值
	//    color: string,		// 数据图形的颜色
	//    percent: number,		// 饼图的百分比
	// }
	Formatter string `json:"formatter,omitempty"`
}

// MarkLine 配置项
type MarkPointOpts struct {
	Data []interface{} `json:"data,omitempty"`
	MPStyleOpts
}

// MarkPoint 风格配置项
type MPStyleOpts struct {
	// 图元的图形类别
	// 可选 "circle", "rect", "roundRect", "triangle", "diamond", "pin", "arrow", "none"
	Symbol string `json:"symbol,omitempty"`
	// 图元的大小
	SymbolSize float32 `json:"symbolSize,omitempty"`
	// 标注文本配置项
	Label LabelTextOpts `json:"label,omitempty"`
}

// MarkPoint 数据 Name-Type
type MPNameTypeItem struct {
	// 标记点名称
	Name string `json:"name"`
	// 内置类型，可选 "average", "min", "max"
	Type string `json:"type"`
	// 在使用 type 时有效，用于指定在哪个维度上指定最大值最小值。
	// 可以是维度的直接名称，例如折线图时可以是 x、angle 等、candlestick 图时可以是 open、close 等维度名称。
	ValueDim string `json:"valueDim,omitempty"`
}

// MarkPoint 数据 Name-Coordinates
type MPNameCoordItem struct {
	// 标记点名称
	Name string `json:"name"`
	// 标记点坐标
	Coord []interface{} `json:"coord"`
	// 在使用 type 时有效，用于指定在哪个维度上指定最大值最小值。
	// 可以是维度的直接名称，例如折线图时可以是 x、angle 等、candlestick 图时可以是 open、close 等维度名称。
	ValueDim string `json:"valueDim,omitempty"`
}

// MarkLine 配置项
type MarkLineOpts struct {
	Data []interface{} `json:"data,omitempty"`
	MLStyleOpts
}

// MarkLine 风格配置项
type MLStyleOpts struct {
	// 图元的图形类别
	// 可选 "circle", "rect", "roundRect", "triangle", "diamond", "pin", "arrow", "none"
	Symbol []string `json:"symbol,omitempty"`
	// 图元的大小
	SymbolSize float32 `json:"symbolSize,omitempty"`
	// 标线文本配置项
	Label LabelTextOpts `json:"label,omitempty"`
}

// MarkLine 数据 Name-Type
type MLNameTypeItem struct {
	// 标记线名称
	Name string `json:"name"`
	// 内置类型，可选 "average", "min", "max"
	Type string `json:"type"`
	// 在使用 type 时有效，用于指定在哪个维度上指定最大值最小值。
	// 可以是维度的直接名称，例如折线图时可以是 x、angle 等、candlestick 图时可以是 open、close 等维度名称。
	ValueDim string `json:"valueDim,omitempty"`
}

// MarkLine 数据 Name-YAxis
type MLNameYAxisItem struct {
	// 标记线名称
	Name string `json:"name"`
	// Y 轴数据
	YAxis interface{} `json:"yAxis"`
	// 在使用 type 时有效，用于指定在哪个维度上指定最大值最小值。
	// 可以是维度的直接名称，例如折线图时可以是 x、angle 等、candlestick 图时可以是 open、close 等维度名称。
	ValueDim string `json:"valueDim,omitempty"`
}

// MarkLine 数据 Name-XAxis
type MLNameXAxisItem struct {
	// 标记线名称
	Name string `json:"name"`
	// X 轴数据
	XAxis interface{} `json:"xAxis"`
	// 在使用 type 时有效，用于指定在哪个维度上指定最大值最小值。
	// 可以是维度的直接名称，例如折线图时可以是 x、angle 等、candlestick 图时可以是 open、close 等维度名称。
	ValueDim string `json:"valueDim,omitempty"`
}

// MarkLine 数据 Name-Coordinates
type MLNameCoordItem struct {
	// 标记线名称
	Name string
	// 标记线起始坐标
	Coord0 []interface{}
	// 标记线结束坐标
	Coord1 []interface{}
	// 在使用 type 时有效，用于指定在哪个维度上指定最大值最小值。
	// 可以是维度的直接名称，例如折线图时可以是 x、angle 等、candlestick 图时可以是 open、close 等维度名称。
	ValueDim string `json:"valueDim,omitempty"`
}

type ItemStyleOpts struct {
	// 图形的颜色
	// Kline 图中为 阳线图形颜色
	Color string `json:"color,omitempty"`
	// Kline 图中为 阴线图形颜色
	Color0 string `json:"color0,omitempty"`
	// 图形的描边颜色
	// Kline 途中为 阳线图形的描边颜色
	BorderColor string `json:"borderColor,omitempty"`
	// Kline 途中为 阴线图形的描边颜色
	BorderColor0 string `json:"borderColor0,omitempty"`
	// 图形透明度。支持从 0 到 1 的数字，为 0 时不绘制该图形
	Opacity float32 `json:"opacity,omitempty"`
}

type singleSeries struct {
	// 系列名称
	Name string `json:"name,omitempty"`
	// 系列类型
	Type string `json:"type"`

	// Rectangular charts
	// 数据堆叠，同个类目轴上系列配置相同的 stack 值后，后一个系列的值会在前一个系列的值上相加
	Stack string `json:"stack,omitempty"`
	// 使用的 X 轴的 index，在单个图表实例中存在多个 X 轴的时候有用
	XAxisIndex int `json:"xAxisIndex,omitempty"`
	// 使用的 Y 轴的 index，在单个图表实例中存在多个 Y 轴的时候有用
	YAxisIndex int `json:"yAxisIndex,omitempty"`

	// Map charts
	MapType     string `json:"map,omitempty"`
	CoordSystem string `json:"coordinateSystem,omitempty"`

	// Line charts
	// 是否是阶梯线图。可以设置为 true 显示成阶梯线图。
	Step bool `json:"step,omitempty"`
	// 是否平滑曲线显示
	Smooth bool `json:"smooth,omitempty"`

	// TODO: example
	// pie charts
	// 是否展示成南丁格尔图，通过半径区分数据大小。可选择两种模式：
	// "radius": 扇区圆心角展现数据的百分比，半径展现数据的大小。
	// "area": 所有扇区圆心角相同，仅通过半径展现数据大小。
	RoseType interface{} `json:"roseType,omitempty"`
	// 饼图的中心（圆心）坐标，数组的第一项是横坐标，第二项是纵坐标。
	// 支持设置成百分比，设置成百分比时第一项是相对于容器宽度，第二项是相对于容器高度。
	// 示例
	// center: [400, 300] 设置成绝对的像素值
	// center: ["50%", "50%"] 设置成相对的百分比
	// 默认
	// ["50%", "50%"]
	Center interface{} `json:"center,omitempty"`
	// 饼图的半径。可以为如下类型：
	// number：直接指定外半径值。
	// string：例如，"20%"，表示外半径为可视区尺寸（容器高宽中较小一项）的 20% 长度。
	// Array.<number|string>：数组的第一项是内半径，第二项是外半径。每一项遵从上述 number string 的描述。
	// 默认
	// [0, "75%"]
	Radius interface{} `json:"radius,omitempty"`

	// wordCloud
	// 词云图形状
	// 可选 "circle", "cardioid", "diamond", "triangle-forward", "triangle", "pentagon", "star"
	// 默认 "circle"
	Shape string `json:"shape,omitempty"`
	// 字体大小范围
	SizeRange []float32 `json:"sizeRange,omitempty"`
	// 字体倾斜角度范围
	RotationRange []float32 `json:"rotationRange,omitempty"`

	// Liquid
	LiquidOutlineOpts `json:"outline,omitempty"`
	IsWaveAnimation   bool `json:"waveAnimation"`

	// 系列数据项
	Data interface{} `json:"data"`

	// 系列配置项
	ItemStyleOpts    `json:"itemStyle,omitempty"`
	LabelTextOpts    `json:"label,omitempty"`
	MarkLineOpts     `json:"markLine,omitempty"`
	MarkPointOpts    `json:"markPoint,omitempty"`
	RippleEffectOpts `json:"rippleEffect,omitempty"`
	LineStyleOpts    `json:"lineStyle,omitempty"`
	AreaStyleOpts    `json:"areaStyle,omitempty"`
	TextStyleOpts    `json:"textStyle,omitempty"`
}

// 设置 singleSeries 配置项
func (s *singleSeries) switchSeriesOpts(options ...interface{}) {
	// 实际 MarkLevel Name Coordinates 结构
	type MLNameCoord struct {
		Name  string        `json:"name,omitempty"`
		Coord []interface{} `json:"coord"`
	}

	for i := 0; i < len(options); i++ {
		option := options[i]
		switch option.(type) {
		case LabelTextOpts:
			s.LabelTextOpts = option.(LabelTextOpts)
		case RippleEffectOpts:
			s.RippleEffectOpts = option.(RippleEffectOpts)
		case LineStyleOpts:
			s.LineStyleOpts = option.(LineStyleOpts)
		case AreaStyleOpts:
			s.AreaStyleOpts = option.(AreaStyleOpts)
		case ItemStyleOpts:
			s.ItemStyleOpts = option.(ItemStyleOpts)
		case TextStyleOpts:
			s.TextStyleOpts = option.(TextStyleOpts)
			s.TextStyleOpts.Normal = &TextStyleOpts{
				Color:     s.TextStyleOpts.Color,
				FontSize:  s.TextStyleOpts.FontSize,
				FontStyle: s.TextStyleOpts.FontStyle,
			}

			// MarkLine 配置项
		case MLNameTypeItem:
			s.MarkLineOpts.Data = append(s.MarkLineOpts.Data, option.(MLNameTypeItem))
		case MLNameXAxisItem:
			s.MarkLineOpts.Data = append(s.MarkLineOpts.Data, option.(MLNameXAxisItem))
		case MLNameYAxisItem:
			s.MarkLineOpts.Data = append(s.MarkLineOpts.Data, option.(MLNameYAxisItem))
		case MLNameCoordItem:
			m := option.(MLNameCoordItem)
			s.MarkLineOpts.Data = append(
				s.MarkLineOpts.Data, []MLNameCoord{{Name: m.Name, Coord: m.Coord0}, {Coord: m.Coord1}})
		case MLStyleOpts:
			s.MarkLineOpts.MLStyleOpts = option.(MLStyleOpts)

			// MarkPoint 配置项
		case MPNameTypeItem:
			s.MarkPointOpts.Data = append(s.MarkPointOpts.Data, option.(MPNameTypeItem))
		case MPNameCoordItem:
			s.MarkPointOpts.Data = append(s.MarkPointOpts.Data, option.(MPNameCoordItem))
		case MPStyleOpts:
			s.MarkPointOpts.MPStyleOpts = option.(MPStyleOpts)

		case BarOpts:
			opt := option.(BarOpts)
			opt.setChartOpt(s)
		case HeatMapOpts:
			opt := option.(HeatMapOpts)
			opt.setChartOpt(s)
		case LineOpts:
			opt := option.(LineOpts)
			opt.setChartOpt(s)
		case LiquidOpts:
			opt := option.(LiquidOpts)
			opt.setChartOpt(s)
		case PieOpts:
			opt := option.(PieOpts)
			opt.setChartOpt(s)
		case WordCLoudOpts:
			opt := option.(WordCLoudOpts)
			opt.setChartOpt(s)
		}
	}
}

func (s *singleSeries) setSingleSeriesOpts(options ...interface{}) {
	s.switchSeriesOpts(options...)
}

type Series []singleSeries

type serieser interface {
	exportSeries() Series
}

func (series *Series) exportSeries() Series {
	return *series
}

func (series *Series) SetSeriesConfig(options ...interface{}) {
	tsl := *series
	for i := 0; i < len(tsl); i++ {
		tsl[i].switchSeriesOpts(options...)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2015 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 metrics

import (
	"bufio"
	"net"
	"net/http"
	"strconv"
	"time"

	utilnet "k8s.io/apimachinery/pkg/util/net"

	"github.com/emicklei/go-restful"
	"github.com/prometheus/client_golang/prometheus"
)

var (
	// TODO(a-robinson): Add unit tests for the handling of these metrics once
	// the upstream library supports it.
	requestCounter = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "apiserver_request_count",
			Help: "Counter of apiserver requests broken out for each verb, API resource, client, and HTTP response contentType and code.",
		},
		[]string{"verb", "resource", "client", "contentType", "code"},
	)
	requestLatencies = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Name: "apiserver_request_latencies",
			Help: "Response latency distribution in microseconds for each verb, resource and client.",
			// Use buckets ranging from 125 ms to 8 seconds.
			Buckets: prometheus.ExponentialBuckets(125000, 2.0, 7),
		},
		[]string{"verb", "resource"},
	)
	requestLatenciesSummary = prometheus.NewSummaryVec(
		prometheus.SummaryOpts{
			Name: "apiserver_request_latencies_summary",
			Help: "Response latency summary in microseconds for each verb and resource.",
			// Make the sliding window of 1h.
			MaxAge: time.Hour,
		},
		[]string{"verb", "resource"},
	)
)

// Register all metrics.
func Register() {
	prometheus.MustRegister(requestCounter)
	prometheus.MustRegister(requestLatencies)
	prometheus.MustRegister(requestLatenciesSummary)
}

func Monitor(verb, resource *string, client, contentType string, httpCode int, reqStart time.Time) {
	elapsed := float64((time.Since(reqStart)) / time.Microsecond)
	requestCounter.WithLabelValues(*verb, *resource, client, contentType, codeToString(httpCode)).Inc()
	requestLatencies.WithLabelValues(*verb, *resource).Observe(elapsed)
	requestLatenciesSummary.WithLabelValues(*verb, *resource).Observe(elapsed)
}

func Reset() {
	requestCounter.Reset()
	requestLatencies.Reset()
	requestLatenciesSummary.Reset()
}

// InstrumentRouteFunc works like Prometheus' InstrumentHandlerFunc but wraps
// the go-restful RouteFunction instead of a HandlerFunc
func InstrumentRouteFunc(verb, resource string, routeFunc restful.RouteFunction) restful.RouteFunction {
	return restful.RouteFunction(func(request *restful.Request, response *restful.Response) {
		now := time.Now()

		delegate := &responseWriterDelegator{ResponseWriter: response.ResponseWriter}

		_, cn := response.ResponseWriter.(http.CloseNotifier)
		_, fl := response.ResponseWriter.(http.Flusher)
		_, hj := response.ResponseWriter.(http.Hijacker)
		var rw http.ResponseWriter
		if cn && fl && hj {
			rw = &fancyResponseWriterDelegator{delegate}
		} else {
			rw = delegate
		}
		response.ResponseWriter = rw

		routeFunc(request, response)
		Monitor(&verb, &resource, utilnet.GetHTTPClient(request.Request), rw.Header().Get("Content-Type"), delegate.status, now)
	})
}

type responseWriterDelegator struct {
	http.ResponseWriter

	status      int
	written     int64
	wroteHeader bool
}

func (r *responseWriterDelegator) WriteHeader(code int) {
	r.status = code
	r.wroteHeader = true
	r.ResponseWriter.WriteHeader(code)
}

func (r *responseWriterDelegator) Write(b []byte) (int, error) {
	if !r.wroteHeader {
		r.WriteHeader(http.StatusOK)
	}
	n, err := r.ResponseWriter.Write(b)
	r.written += int64(n)
	return n, err
}

type fancyResponseWriterDelegator struct {
	*responseWriterDelegator
}

func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool {
	return f.ResponseWriter.(http.CloseNotifier).CloseNotify()
}

func (f *fancyResponseWriterDelegator) Flush() {
	f.ResponseWriter.(http.Flusher).Flush()
}

func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	return f.ResponseWriter.(http.Hijacker).Hijack()
}

// Small optimization over Itoa
func codeToString(s int) string {
	switch s {
	case 100:
		return "100"
	case 101:
		return "101"

	case 200:
		return "200"
	case 201:
		return "201"
	case 202:
		return "202"
	case 203:
		return "203"
	case 204:
		return "204"
	case 205:
		return "205"
	case 206:
		return "206"

	case 300:
		return "300"
	case 301:
		return "301"
	case 302:
		return "302"
	case 304:
		return "304"
	case 305:
		return "305"
	case 307:
		return "307"

	case 400:
		return "400"
	case 401:
		return "401"
	case 402:
		return "402"
	case 403:
		return "403"
	case 404:
		return "404"
	case 405:
		return "405"
	case 406:
		return "406"
	case 407:
		return "407"
	case 408:
		return "408"
	case 409:
		return "409"
	case 410:
		return "410"
	case 411:
		return "411"
	case 412:
		return "412"
	case 413:
		return "413"
	case 414:
		return "414"
	case 415:
		return "415"
	case 416:
		return "416"
	case 417:
		return "417"
	case 418:
		return "418"

	case 500:
		return "500"
	case 501:
		return "501"
	case 502:
		return "502"
	case 503:
		return "503"
	case 504:
		return "504"
	case 505:
		return "505"

	case 428:
		return "428"
	case 429:
		return "429"
	case 431:
		return "431"
	case 511:
		return "511"

	default:
		return strconv.Itoa(s)
	}
}
<commit_msg>tweak a list to a watch verb when requested<commit_after>/*
Copyright 2015 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 metrics

import (
	"bufio"
	"net"
	"net/http"
	"strconv"
	"strings"
	"time"

	utilnet "k8s.io/apimachinery/pkg/util/net"

	"github.com/emicklei/go-restful"
	"github.com/prometheus/client_golang/prometheus"
)

var (
	// TODO(a-robinson): Add unit tests for the handling of these metrics once
	// the upstream library supports it.
	requestCounter = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "apiserver_request_count",
			Help: "Counter of apiserver requests broken out for each verb, API resource, client, and HTTP response contentType and code.",
		},
		[]string{"verb", "resource", "client", "contentType", "code"},
	)
	requestLatencies = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Name: "apiserver_request_latencies",
			Help: "Response latency distribution in microseconds for each verb, resource and client.",
			// Use buckets ranging from 125 ms to 8 seconds.
			Buckets: prometheus.ExponentialBuckets(125000, 2.0, 7),
		},
		[]string{"verb", "resource"},
	)
	requestLatenciesSummary = prometheus.NewSummaryVec(
		prometheus.SummaryOpts{
			Name: "apiserver_request_latencies_summary",
			Help: "Response latency summary in microseconds for each verb and resource.",
			// Make the sliding window of 1h.
			MaxAge: time.Hour,
		},
		[]string{"verb", "resource"},
	)
)

// Register all metrics.
func Register() {
	prometheus.MustRegister(requestCounter)
	prometheus.MustRegister(requestLatencies)
	prometheus.MustRegister(requestLatenciesSummary)
}

func Monitor(verb, resource *string, client, contentType string, httpCode int, reqStart time.Time) {
	elapsed := float64((time.Since(reqStart)) / time.Microsecond)
	requestCounter.WithLabelValues(*verb, *resource, client, contentType, codeToString(httpCode)).Inc()
	requestLatencies.WithLabelValues(*verb, *resource).Observe(elapsed)
	requestLatenciesSummary.WithLabelValues(*verb, *resource).Observe(elapsed)
}

func Reset() {
	requestCounter.Reset()
	requestLatencies.Reset()
	requestLatenciesSummary.Reset()
}

// InstrumentRouteFunc works like Prometheus' InstrumentHandlerFunc but wraps
// the go-restful RouteFunction instead of a HandlerFunc
func InstrumentRouteFunc(verb, resource string, routeFunc restful.RouteFunction) restful.RouteFunction {
	return restful.RouteFunction(func(request *restful.Request, response *restful.Response) {
		now := time.Now()

		delegate := &responseWriterDelegator{ResponseWriter: response.ResponseWriter}

		_, cn := response.ResponseWriter.(http.CloseNotifier)
		_, fl := response.ResponseWriter.(http.Flusher)
		_, hj := response.ResponseWriter.(http.Hijacker)
		var rw http.ResponseWriter
		if cn && fl && hj {
			rw = &fancyResponseWriterDelegator{delegate}
		} else {
			rw = delegate
		}
		response.ResponseWriter = rw

		routeFunc(request, response)

		if verb == "LIST" && strings.ToLower(request.QueryParameter("watch")) == "true" {
			verb = "WATCH"
		}
		Monitor(&verb, &resource, utilnet.GetHTTPClient(request.Request), rw.Header().Get("Content-Type"), delegate.status, now)
	})
}

type responseWriterDelegator struct {
	http.ResponseWriter

	status      int
	written     int64
	wroteHeader bool
}

func (r *responseWriterDelegator) WriteHeader(code int) {
	r.status = code
	r.wroteHeader = true
	r.ResponseWriter.WriteHeader(code)
}

func (r *responseWriterDelegator) Write(b []byte) (int, error) {
	if !r.wroteHeader {
		r.WriteHeader(http.StatusOK)
	}
	n, err := r.ResponseWriter.Write(b)
	r.written += int64(n)
	return n, err
}

type fancyResponseWriterDelegator struct {
	*responseWriterDelegator
}

func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool {
	return f.ResponseWriter.(http.CloseNotifier).CloseNotify()
}

func (f *fancyResponseWriterDelegator) Flush() {
	f.ResponseWriter.(http.Flusher).Flush()
}

func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	return f.ResponseWriter.(http.Hijacker).Hijack()
}

// Small optimization over Itoa
func codeToString(s int) string {
	switch s {
	case 100:
		return "100"
	case 101:
		return "101"

	case 200:
		return "200"
	case 201:
		return "201"
	case 202:
		return "202"
	case 203:
		return "203"
	case 204:
		return "204"
	case 205:
		return "205"
	case 206:
		return "206"

	case 300:
		return "300"
	case 301:
		return "301"
	case 302:
		return "302"
	case 304:
		return "304"
	case 305:
		return "305"
	case 307:
		return "307"

	case 400:
		return "400"
	case 401:
		return "401"
	case 402:
		return "402"
	case 403:
		return "403"
	case 404:
		return "404"
	case 405:
		return "405"
	case 406:
		return "406"
	case 407:
		return "407"
	case 408:
		return "408"
	case 409:
		return "409"
	case 410:
		return "410"
	case 411:
		return "411"
	case 412:
		return "412"
	case 413:
		return "413"
	case 414:
		return "414"
	case 415:
		return "415"
	case 416:
		return "416"
	case 417:
		return "417"
	case 418:
		return "418"

	case 500:
		return "500"
	case 501:
		return "501"
	case 502:
		return "502"
	case 503:
		return "503"
	case 504:
		return "504"
	case 505:
		return "505"

	case 428:
		return "428"
	case 429:
		return "429"
	case 431:
		return "431"
	case 511:
		return "511"

	default:
		return strconv.Itoa(s)
	}
}
<|endoftext|>


<commit_before>package cors

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
	"net/url"
	"strings"
)

var (
	validSites          = map[string]bool{}
	defaultAllowHeaders = []string{"Origin", "Accept", "Content-Type", "Authorization", "ib_id"}
	defaultAllowMethods []string
)

// CORS will set the headers for Cross-origin resource sharing
func CORS() gin.HandlerFunc {
	return func(c *gin.Context) {

		req := c.Request
		origin := req.Header.Get("Origin")

		// Set origin header from sites config
		if isAllowedSite(origin) {
			c.Header("Access-Control-Allow-Origin", origin)
		} else {
			c.Header("Access-Control-Allow-Origin", "")
		}

		c.Header("Access-Control-Allow-Credentials", "true")

		if req.Method == "OPTIONS" {

			// Add allowed method header
			c.Header("Access-Control-Allow-Methods", strings.Join(defaultAllowMethods, ","))

			// Add allowed headers header
			c.Header("Access-Control-Allow-Headers", strings.Join(defaultAllowHeaders, ","))

			c.AbortWithStatus(http.StatusOK)

			return

		} else {

			c.Next()

		}

	}
}

func SetDomains(domains, methods []string) {
	// add valid sites to map
	for _, site := range domains {
		validSites[site] = true
	}

	// set methods
	defaultAllowMethods = methods

	fmt.Println(strings.Repeat("*", 60))
	fmt.Printf("%-20v\n\n", "CORS")
	fmt.Printf("%-20v%40v\n", "Domains", strings.Join(domains, ", "))
	fmt.Printf("%-20v%40v\n", "Methods", strings.Join(defaultAllowMethods, ", "))
	fmt.Println(strings.Repeat("*", 60))

	return
}

// Check if origin is allowed
func isAllowedSite(host string) bool {

	// Get the host from the origin
	parsed, err := url.Parse(host)
	if err != nil {
		return false
	}

	if validSites[strings.ToLower(parsed.Host)] {
		return true
	}

	return false

}
<commit_msg>add max-age to cors headers<commit_after>package cors

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
	"net/url"
	"strings"
)

var (
	validSites          = map[string]bool{}
	defaultAllowHeaders = []string{"Origin", "Accept", "Content-Type", "Authorization", "ib_id"}
	defaultAllowMethods []string
)

// CORS will set the headers for Cross-origin resource sharing
func CORS() gin.HandlerFunc {
	return func(c *gin.Context) {

		req := c.Request
		origin := req.Header.Get("Origin")

		// Set origin header from sites config
		if isAllowedSite(origin) {
			c.Header("Access-Control-Allow-Origin", origin)
		} else {
			c.Header("Access-Control-Allow-Origin", "")
		}

		c.Header("Vary", "Origin")

		c.Header("Access-Control-Allow-Credentials", "true")

		if req.Method == "OPTIONS" {

			// Add allowed method header
			c.Header("Access-Control-Allow-Methods", strings.Join(defaultAllowMethods, ","))

			// Add allowed headers header
			c.Header("Access-Control-Allow-Headers", strings.Join(defaultAllowHeaders, ","))

			c.Header("Access-Control-Max-Age", "600")

			c.AbortWithStatus(http.StatusOK)

			return

		} else {

			c.Next()

		}

	}
}

func SetDomains(domains, methods []string) {
	// add valid sites to map
	for _, site := range domains {
		validSites[site] = true
	}

	// set methods
	defaultAllowMethods = methods

	fmt.Println(strings.Repeat("*", 60))
	fmt.Printf("%-20v\n\n", "CORS")
	fmt.Printf("%-20v%40v\n", "Domains", strings.Join(domains, ", "))
	fmt.Printf("%-20v%40v\n", "Methods", strings.Join(defaultAllowMethods, ", "))
	fmt.Println(strings.Repeat("*", 60))

	return
}

// Check if origin is allowed
func isAllowedSite(host string) bool {

	// Get the host from the origin
	parsed, err := url.Parse(host)
	if err != nil {
		return false
	}

	if validSites[strings.ToLower(parsed.Host)] {
		return true
	}

	return false

}
<|endoftext|>


<commit_before>package crawler

import (
	"fmt"
	"github.com/ak1t0/flame/format"
	"golang.org/x/net/proxy"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
	"time"
)

func Crawl(logs format.OnionLogs) format.OnionLogs {
	var scanned format.OnionLogs

	// data channel
	c := make(chan format.OnionLog, 3000)
	// semaphore
	sem := 10
	semc := make(chan bool, sem)

	// crawl concurrently
	for _, v := range logs {
		semc <- true
		go func(v format.OnionLog) {
			defer func() { <-semc }()
			v.Result = scan(v.Address)
			c <- v
		}(v)
	}

	// crawled data
	for i := 0; i < len(logs); i++ {
		data := <-c
		scanned = append(scanned, data)
	}

	return scanned
}

func scan(target string) string {
	hostPort := "127.0.0.1:9050"
	p, err := proxy.SOCKS5("tcp", hostPort, nil, proxy.Direct)
	if err != nil {
		fmt.Println(err)
	}

	client := http.DefaultClient
	client.Transport = &http.Transport{
		Dial: p.Dial,
	}

	// connection and timeout
	c := make(chan string, 1)
	limit := 30

	go func() {
		resp, err := client.Get("http://" + target)
		if err != nil {
			log.Println(err)

			e := err.Error()
			if strings.Contains(e, "connection refused") {
				c <- "connection refused"
			} else if strings.Contains(e, "connection forbidden") {
				c <- "connection forbidden"
			} else if strings.Contains(e, "host unreachable") {
				c <- "general failure"
			} else if strings.Contains(e, "TTL expired") {
				c <- "TTL expired"
			} else {
				c <- e //"Unknown Error"
			}
		}
		defer resp.Body.Close()
		contents, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			c <- "available"
		}
		c <- string(contents)
	}()

	select {
	case res := <-c:
		return res
	// timeout
	case <-time.After(time.Second * time.Duration(limit)):
		return "timeout"
	}
}
<commit_msg>fix error handling bug<commit_after>package crawler

import (
	"fmt"
	"github.com/ak1t0/flame/format"
	"golang.org/x/net/proxy"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
	"time"
)

func Crawl(logs format.OnionLogs) format.OnionLogs {
	var scanned format.OnionLogs

	// data channel
	c := make(chan format.OnionLog, 3000)
	// semaphore
	sem := 10
	semc := make(chan bool, sem)

	// crawl concurrently
	for _, v := range logs {
		semc <- true
		go func(v format.OnionLog) {
			defer func() { <-semc }()
			v.Result = scan(v.Address)
			c <- v
		}(v)
	}

	// crawled data
	for i := 0; i < len(logs); i++ {
		data := <-c
		scanned = append(scanned, data)
	}

	return scanned
}

func scan(target string) string {
	hostPort := "127.0.0.1:9050"
	p, err := proxy.SOCKS5("tcp", hostPort, nil, proxy.Direct)
	if err != nil {
		fmt.Println(err)
	}

	client := http.DefaultClient
	client.Transport = &http.Transport{
		Dial: p.Dial,
	}

	// connection and timeout
	c := make(chan string, 1)
	limit := 30

	go func() {
		resp, err := client.Get("http://" + target)

		if err != nil {
			log.Println(err)

			e := err.Error()
			if strings.Contains(e, "connection refused") {
				c <- "connection refused"
			} else if strings.Contains(e, "connection forbidden") {
				c <- "connection forbidden"
			} else if strings.Contains(e, "host unreachable") {
				c <- "general failure"
			} else if strings.Contains(e, "TTL expired") {
				c <- "TTL expired"
			} else {
				c <- e //"Unknown Error"
			}

			return
		}

		contents, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			c <- "available"
		}
		c <- string(contents)
		return
	}()

	select {
	case res := <-c:
		return res
	// timeout
	case <-time.After(time.Second * time.Duration(limit)):
		return "timeout"
	}
}
<|endoftext|>


<commit_before>package mockingbird

import (
	"github.com/gonum/matrix/mat64"
	"github.com/lazywei/liblinear"
)

// type Prediction struct {
// 	Label    int
// 	Language string
// 	Score    float64
// }

type LogisticRegression struct {
	model *liblinear.Model
}

func NewLogisticRegression() *LogisticRegression {
	return &LogisticRegression{}
}

func (lr *LogisticRegression) Fit(X, y *mat64.Dense) {
	model := liblinear.Train(X, y, 1, liblinear.L2R_LR, 0.01, 1, 0.1, nil)
	lr.model = model
}

func (lr *LogisticRegression) Predict(X *mat64.Dense) []Prediction {
	nSamples, _ := X.Dims()

	prediction := []Prediction{}

	for i := 0; i < nSamples; i++ {
		scores := liblinear.PredictProba(lr.model, X)
		_, nClasses := scores.Dims()

		bestScore := scores.At(i, 0)
		bestLangIdx := 0

		for langIdx := 0; langIdx < nClasses; langIdx++ {
			score := scores.At(i, langIdx)
			if score > bestScore {
				bestScore = score
				bestLangIdx = langIdx
			}
		}

		prediction = append(prediction, Prediction{
			Label:    bestLangIdx,
			Language: "TODO: PENDING",
			Score:    bestScore,
		})
	}

	return prediction
}

// func (nb *NaiveBayes) ToGob() string {
// 	var output bytes.Buffer

// 	params := nb.params

// 	enc := gob.NewEncoder(&output)

// 	err := enc.Encode(params)
// 	if err != nil {
// 		log.Fatal("encode error:", err)
// 	}

// 	return output.String()
// }

// func NewNaiveBayesFromGob(gobStr string) *NaiveBayes {
// 	var params nbParams

// 	input := bytes.NewBufferString(gobStr)

// 	dec := gob.NewDecoder(input)

// 	err := dec.Decode(&params)
// 	if err != nil {
// 		log.Fatal(err)
// 	}

// 	nb := NewNaiveBayes()
// 	nb.params = params
// 	return nb
// }
<commit_msg>Adopt liblinear update.<commit_after>package mockingbird

import (
	"github.com/gonum/matrix/mat64"
	"github.com/lazywei/liblinear"
)

// type Prediction struct {
// 	Label    int
// 	Language string
// 	Score    float64
// }

type LogisticRegression struct {
	model *liblinear.Model
}

func NewLogisticRegression() *LogisticRegression {
	return &LogisticRegression{}
}

func (lr *LogisticRegression) Fit(X, y *mat64.Dense) {
	model := liblinear.Train(X, y, 1, &liblinear.Parameter{
		Eps: 0.01, C: 1, P: 0.1,
		SolverType: liblinear.L2R_LR,
	})
	lr.model = model
}

func (lr *LogisticRegression) Predict(X *mat64.Dense) []Prediction {
	nSamples, _ := X.Dims()

	prediction := []Prediction{}

	for i := 0; i < nSamples; i++ {
		scores := liblinear.PredictProba(lr.model, X)
		_, nClasses := scores.Dims()

		bestScore := scores.At(i, 0)
		bestLangIdx := 0

		for langIdx := 0; langIdx < nClasses; langIdx++ {
			score := scores.At(i, langIdx)
			if score > bestScore {
				bestScore = score
				bestLangIdx = langIdx
			}
		}

		prediction = append(prediction, Prediction{
			Label:    bestLangIdx,
			Language: "TODO: PENDING",
			Score:    bestScore,
		})
	}

	return prediction
}

// func (nb *NaiveBayes) ToGob() string {
// 	var output bytes.Buffer

// 	params := nb.params

// 	enc := gob.NewEncoder(&output)

// 	err := enc.Encode(params)
// 	if err != nil {
// 		log.Fatal("encode error:", err)
// 	}

// 	return output.String()
// }

// func NewNaiveBayesFromGob(gobStr string) *NaiveBayes {
// 	var params nbParams

// 	input := bytes.NewBufferString(gobStr)

// 	dec := gob.NewDecoder(input)

// 	err := dec.Decode(&params)
// 	if err != nil {
// 		log.Fatal(err)
// 	}

// 	nb := NewNaiveBayes()
// 	nb.params = params
// 	return nb
// }
<|endoftext|>


<commit_before>package data

import (
	"encoding/json"
	"strconv"
	"time"

	"gopkg.in/redis.v5"
)

// An EmailTokenType describes different types of email token
type EmailTokenType int

// Define the default EmailTokenType.
const (
	EmailTokenNone EmailTokenType = iota
	EmailTokenResetPassword
	EmailTokenChangeEmail
	EmailTokenVerifyEmail
)

// An EmailToken is used for situations like an email change or a password reset, where a confirmation email must be sent.
type EmailToken struct {
	Token    string         `json:"token"`
	Type     EmailTokenType `json:"type"`
	Metadata string         `json:"metadata"`
	UserID   int            `json:"userID"`
}

type User struct {
	ID                 int          `json:"id"`
	Name               string       `json:"name"`
	Email              string       `json:"email"`
	PasswordHash       string       `json:"-"`
	Type               string       `json:"type"`
	Features           string       `json:"features"`
	Level              int          `json:"level"`
	EmailVerified      bool         `json:"emailVerified"`
	ShowMigrateMessage int          `json:"showMigrateMessage"`
	CreatedAt          int          `json:"createdAt"`
	LastLoginAt        int          `json:"lastLoginAt"`
	Schools            []SchoolInfo `json:"schools"`
}

type Tab struct {
	ID     int    `json:"id"`
	Slug   string `json:"slug"`
	Icon   string `json:"icon"`
	Label  string `json:"label"`
	Target string `json:"target"`
}

// GetEmailToken fetches the given email token.
func GetEmailToken(token string) (EmailToken, error) {
	storedToken, err := RedisClient.HGetAll("token:" + token).Result()
	if err == redis.Nil {
		return EmailToken{}, ErrNotFound
	} else if err != nil {
		return EmailToken{}, err
	}

	if storedToken["type"] == "" {
		return EmailToken{}, ErrNotFound
	}

	tokenType, err := strconv.Atoi(storedToken["type"])
	if err != nil {
		return EmailToken{}, err
	}

	userID, err := strconv.Atoi(storedToken["userID"])
	if err != nil {
		return EmailToken{}, err
	}

	response := EmailToken{
		Token:    token,
		Type:     EmailTokenType(tokenType),
		Metadata: storedToken["metadata"],
		UserID:   userID,
	}

	return response, nil
}

// GetTabsByUserID fetches all tabs that the given user has access to.
func GetTabsByUserID(userID int) ([]Tab, error) {
	rows, err := DB.Query("SELECT tabs.id, tabs.slug, tabs.icon, tabs.label, tabs.target FROM tabs INNER JOIN tab_permissions ON tab_permissions.tabId = tabs.id WHERE tab_permissions.userId = ?", userID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	tabs := []Tab{}
	for rows.Next() {
		tab := Tab{}
		err := rows.Scan(&tab.ID, &tab.Slug, &tab.Icon, &tab.Label, &tab.Target)
		if err != nil {
			return nil, err
		}
		tabs = append(tabs, tab)
	}
	return tabs, nil
}

// UserExistsWithEmail checks for a user with the given email address. If one exists, it returns the ID of that user.
func UserExistsWithEmail(email string) (bool, int, error) {
	rows, err := DB.Query("SELECT id FROM users WHERE email = ?", email)
	if err != nil {
		return false, -1, err
	}

	defer rows.Close()

	if rows.Next() {
		id := -1
		err = rows.Scan(&id)
		if err != nil {
			return false, -1, err
		}
		return true, id, nil
	}

	return false, -1, nil
}

// GetUserByID fetches data for the given user ID.
func GetUserByID(id int) (User, error) {
	rows, err := DB.Query("SELECT id, name, email, password, type, features, emailVerified, level, showMigrateMessage, createdAt, lastLoginAt FROM users WHERE id = ?", id)
	if err != nil {
		return User{}, err
	}

	if rows.Next() {
		user := User{}
		emailVerified := 0

		err := rows.Scan(&user.ID, &user.Name, &user.Email, &user.PasswordHash, &user.Type, &user.Features, &emailVerified, &user.Level, &user.ShowMigrateMessage, &user.CreatedAt, &user.LastLoginAt)
		if err != nil {
			return User{}, err
		}

		if emailVerified == 1 {
			user.EmailVerified = true
		}

		rows.Close()

		user.Schools = []SchoolInfo{}

		schoolRows, err := DB.Query("SELECT id, schoolId, enabled, data, userId FROM schools WHERE userId = ?", user.ID)
		if err != nil {
			return User{}, err
		}

		for schoolRows.Next() {
			info := SchoolInfo{}
			dataString := ""

			schoolRows.Scan(&info.EnrollmentID, &info.SchoolID, &info.Enabled, &dataString, &info.UserID)

			// parse JSON data
			data := map[string]interface{}{}

			err := json.Unmarshal([]byte(dataString), &data)
			if err != nil {
				return User{}, err
			}

			// get school and hydrate it
			school, err := MainRegistry.GetSchoolByID(info.SchoolID)
			if err != nil {
				return User{}, err
			}

			school.Hydrate(data)

			// set SchoolInfo
			info.DisplayName = school.Name()
			info.ShortName = school.ShortName()
			info.UserDetails = school.UserDetails()
			info.EmailAddress = school.EmailAddress()
			info.School = school

			user.Schools = append(user.Schools, info)
		}

		schoolRows.Close()

		return user, nil
	} else {
		rows.Close()
		return User{}, ErrNotFound
	}
}

// DeleteEmailToken deletes the given email token.
func DeleteEmailToken(token EmailToken) error {
	return RedisClient.Del("token:" + token.Token).Err()
}

// SaveEmailToken saves the given email token with a default expiry.
func SaveEmailToken(token EmailToken) error {
	err := RedisClient.HMSet("token:"+token.Token, map[string]string{
		"type":     strconv.Itoa(int(token.Type)),
		"metadata": token.Metadata,
		"userID":   strconv.Itoa(token.UserID),
	}).Err()

	if err != nil {
		return err
	}

	return RedisClient.Expire("token:"+token.Token, 1*time.Hour).Err()
}
<commit_msg>small cleanup of GetUserByID, making sure rows are always closed<commit_after>package data

import (
	"encoding/json"
	"strconv"
	"time"

	"gopkg.in/redis.v5"
)

// An EmailTokenType describes different types of email token
type EmailTokenType int

// Define the default EmailTokenType.
const (
	EmailTokenNone EmailTokenType = iota
	EmailTokenResetPassword
	EmailTokenChangeEmail
	EmailTokenVerifyEmail
)

// An EmailToken is used for situations like an email change or a password reset, where a confirmation email must be sent.
type EmailToken struct {
	Token    string         `json:"token"`
	Type     EmailTokenType `json:"type"`
	Metadata string         `json:"metadata"`
	UserID   int            `json:"userID"`
}

type User struct {
	ID                 int          `json:"id"`
	Name               string       `json:"name"`
	Email              string       `json:"email"`
	PasswordHash       string       `json:"-"`
	Type               string       `json:"type"`
	Features           string       `json:"features"`
	Level              int          `json:"level"`
	EmailVerified      bool         `json:"emailVerified"`
	ShowMigrateMessage int          `json:"showMigrateMessage"`
	CreatedAt          int          `json:"createdAt"`
	LastLoginAt        int          `json:"lastLoginAt"`
	Schools            []SchoolInfo `json:"schools"`
}

type Tab struct {
	ID     int    `json:"id"`
	Slug   string `json:"slug"`
	Icon   string `json:"icon"`
	Label  string `json:"label"`
	Target string `json:"target"`
}

// GetEmailToken fetches the given email token.
func GetEmailToken(token string) (EmailToken, error) {
	storedToken, err := RedisClient.HGetAll("token:" + token).Result()
	if err == redis.Nil {
		return EmailToken{}, ErrNotFound
	} else if err != nil {
		return EmailToken{}, err
	}

	if storedToken["type"] == "" {
		return EmailToken{}, ErrNotFound
	}

	tokenType, err := strconv.Atoi(storedToken["type"])
	if err != nil {
		return EmailToken{}, err
	}

	userID, err := strconv.Atoi(storedToken["userID"])
	if err != nil {
		return EmailToken{}, err
	}

	response := EmailToken{
		Token:    token,
		Type:     EmailTokenType(tokenType),
		Metadata: storedToken["metadata"],
		UserID:   userID,
	}

	return response, nil
}

// GetTabsByUserID fetches all tabs that the given user has access to.
func GetTabsByUserID(userID int) ([]Tab, error) {
	rows, err := DB.Query("SELECT tabs.id, tabs.slug, tabs.icon, tabs.label, tabs.target FROM tabs INNER JOIN tab_permissions ON tab_permissions.tabId = tabs.id WHERE tab_permissions.userId = ?", userID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	tabs := []Tab{}
	for rows.Next() {
		tab := Tab{}
		err := rows.Scan(&tab.ID, &tab.Slug, &tab.Icon, &tab.Label, &tab.Target)
		if err != nil {
			return nil, err
		}
		tabs = append(tabs, tab)
	}
	return tabs, nil
}

// UserExistsWithEmail checks for a user with the given email address. If one exists, it returns the ID of that user.
func UserExistsWithEmail(email string) (bool, int, error) {
	rows, err := DB.Query("SELECT id FROM users WHERE email = ?", email)
	if err != nil {
		return false, -1, err
	}

	defer rows.Close()

	if rows.Next() {
		id := -1
		err = rows.Scan(&id)
		if err != nil {
			return false, -1, err
		}
		return true, id, nil
	}

	return false, -1, nil
}

// GetUserByID fetches data for the given user ID.
func GetUserByID(id int) (User, error) {
	rows, err := DB.Query("SELECT id, name, email, password, type, features, emailVerified, level, showMigrateMessage, createdAt, lastLoginAt FROM users WHERE id = ?", id)
	if err != nil {
		return User{}, err
	}

	defer rows.Close()

	if !rows.Next() {
		return User{}, ErrNotFound
	}

	user := User{}
	emailVerified := 0

	err = rows.Scan(&user.ID, &user.Name, &user.Email, &user.PasswordHash, &user.Type, &user.Features, &emailVerified, &user.Level, &user.ShowMigrateMessage, &user.CreatedAt, &user.LastLoginAt)
	if err != nil {
		return User{}, err
	}

	if emailVerified == 1 {
		user.EmailVerified = true
	}

	user.Schools = []SchoolInfo{}

	schoolRows, err := DB.Query("SELECT id, schoolId, enabled, data, userId FROM schools WHERE userId = ?", user.ID)
	if err != nil {
		return User{}, err
	}
	defer schoolRows.Close()

	for schoolRows.Next() {
		info := SchoolInfo{}
		dataString := ""

		err := schoolRows.Scan(&info.EnrollmentID, &info.SchoolID, &info.Enabled, &dataString, &info.UserID)
		if err != nil {
			return User{}, err
		}

		// parse JSON data
		data := map[string]interface{}{}

		err = json.Unmarshal([]byte(dataString), &data)
		if err != nil {
			return User{}, err
		}

		// get school and hydrate it
		school, err := MainRegistry.GetSchoolByID(info.SchoolID)
		if err != nil {
			return User{}, err
		}

		school.Hydrate(data)

		// set SchoolInfo
		info.DisplayName = school.Name()
		info.ShortName = school.ShortName()
		info.UserDetails = school.UserDetails()
		info.EmailAddress = school.EmailAddress()
		info.School = school

		user.Schools = append(user.Schools, info)
	}

	return user, nil
}

// DeleteEmailToken deletes the given email token.
func DeleteEmailToken(token EmailToken) error {
	return RedisClient.Del("token:" + token.Token).Err()
}

// SaveEmailToken saves the given email token with a default expiry.
func SaveEmailToken(token EmailToken) error {
	err := RedisClient.HMSet("token:"+token.Token, map[string]string{
		"type":     strconv.Itoa(int(token.Type)),
		"metadata": token.Metadata,
		"userID":   strconv.Itoa(token.UserID),
	}).Err()

	if err != nil {
		return err
	}

	return RedisClient.Expire("token:"+token.Token, 1*time.Hour).Err()
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Huawei Technologies Co., Ltd. All Rights Reserved.
//
// 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.

/*
This module implements cinder driver for OpenSDS. Cinder driver will pass
these operation requests about volume to gophercloud which is an OpenStack
Go SDK.

*/

package cinder

import (
	"time"

	log "github.com/golang/glog"
	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
	"github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerstats"
	"github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions"
	"github.com/gophercloud/gophercloud/openstack/blockstorage/noauth"
	snapshotsv2 "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots"
	volumesv2 "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
	. "github.com/opensds/opensds/contrib/drivers/utils/config"
	pb "github.com/opensds/opensds/pkg/dock/proto"
	"github.com/opensds/opensds/pkg/model"
	"github.com/opensds/opensds/pkg/utils/config"
	"github.com/satori/go.uuid"
)

const (
	defaultConfPath = "/etc/opensds/driver/cinder.yaml"
)

var conf = CinderConfig{}

// Driver is a struct of Cinder backend, which can be called to manage block
// storage service defined in gophercloud.
type Driver struct {
	// Current block storage version
	blockStoragev2 *gophercloud.ServiceClient
	blockStoragev3 *gophercloud.ServiceClient

	conf *CinderConfig
}

// AuthOptions
type AuthOptions struct {
	NoAuth           bool   `yaml:"noAuth,omitempty"`
	CinderEndpoint   string `yaml:"cinderEndpoint,omitempty"`
	IdentityEndpoint string `yaml:"endpoint,omitempty"`
	DomainID         string `yaml:"domainId,omitempty"`
	DomainName       string `yaml:"domainName,omitempty"`
	Username         string `yaml:"username,omitempty"`
	Password         string `yaml:"password,omitempty"`
	TenantID         string `yaml:"tenantId,omitempty"`
	TenantName       string `yaml:"tenantName,omitempty"`
}

// CinderConfig
type CinderConfig struct {
	AuthOptions `yaml:"authOptions"`
	Pool        map[string]PoolProperties `yaml:"pool,flow"`
}

// Setup
func (d *Driver) Setup() error {
	// Read cinder config file
	d.conf = &CinderConfig{}
	p := config.CONF.OsdsDock.Backends.Cinder.ConfigPath
	if "" == p {
		p = defaultConfPath
	}
	Parse(d.conf, p)

	opts := gophercloud.AuthOptions{
		IdentityEndpoint: d.conf.IdentityEndpoint,
		DomainID:         d.conf.DomainID,
		DomainName:       d.conf.DomainName,
		Username:         d.conf.Username,
		Password:         d.conf.Password,
		TenantID:         d.conf.TenantID,
		TenantName:       d.conf.TenantName,
	}

	if d.conf.NoAuth {
		provider, err := noauth.NewClient(opts)
		if err != nil {
			log.Error("When get no authentication options:", err)
			return err
		}

		d.blockStoragev2, err = noauth.NewBlockStorageV2(provider, noauth.EndpointOpts{
			CinderEndpoint: d.conf.CinderEndpoint,
		})
		if err != nil {
			log.Error("When get no authentication block storage session:", err)
			return err
		}
	} else {
		provider, err := openstack.AuthenticatedClient(opts)
		if err != nil {
			log.Error("When get auth options:", err)
			return err
		}

		d.blockStoragev2, err = openstack.NewBlockStorageV2(provider, gophercloud.EndpointOpts{})
		if err != nil {
			log.Error("When get block storage session:", err)
			return err
		}
	}

	return nil
}

// Unset
func (d *Driver) Unset() error { return nil }

// CreateVolume
func (d *Driver) CreateVolume(req *pb.CreateVolumeOpts) (*model.VolumeSpec, error) {
	//Configure create request body.
	opts := &volumesv2.CreateOpts{
		Name:             req.GetName(),
		Description:      req.GetDescription(),
		Size:             int(req.GetSize()),
		AvailabilityZone: req.GetAvailabilityZone(),
	}

	vol, err := volumesv2.Create(d.blockStoragev2, opts).Extract()
	if err != nil {
		log.Error("Cannot create volume:", err)
		return nil, err
	}

	// Currently dock framework doesn't support sync data from storage system,
	// therefore, it's necessary to wait for the result of resource's creation.
	// Timout after 10s.
	timeout := time.After(10 * time.Second)
	ticker := time.NewTicker(300 * time.Millisecond)
	done := make(chan bool, 1)
	go func() {
		for {
			select {
			case <-ticker.C:
				tmpVol, err := d.PullVolume(vol.ID)
				if err != nil {
					continue
				}
				if tmpVol.Status != "creating" {
					vol.Status = tmpVol.Status
					close(done)
					return
				}
			case <-timeout:
				close(done)
				return
			}

		}
	}()
	<-done

	return &model.VolumeSpec{
		BaseModel: &model.BaseModel{
			Id: req.GetId(),
		},
		Name:             vol.Name,
		Description:      vol.Description,
		Size:             int64(vol.Size),
		AvailabilityZone: vol.AvailabilityZone,
		Status:           vol.Status,
	}, nil
}

// PullVolume
func (d *Driver) PullVolume(volID string) (*model.VolumeSpec, error) {
	vol, err := volumesv2.Get(d.blockStoragev2, volID).Extract()
	if err != nil {
		log.Error("Cannot get volume:", err)
		return nil, err
	}

	return &model.VolumeSpec{
		BaseModel: &model.BaseModel{
			Id: vol.ID,
		},
		Name:             vol.Name,
		Description:      vol.Description,
		Size:             int64(vol.Size),
		AvailabilityZone: vol.AvailabilityZone,
		Status:           vol.Status,
	}, nil
}

// DeleteVolume
func (d *Driver) DeleteVolume(opt *pb.DeleteVolumeOpts) error {
	if err := volumesv2.Delete(d.blockStoragev2, opt.GetId()).ExtractErr(); err != nil {
		log.Error("Cannot delete volume:", err)
		return err
	}

	return nil
}

// ExtendVolume ...
func (d *Driver) ExtendVolume(req *pb.ExtendVolumeOpts) (*model.VolumeSpec, error) {
	//Configure create request body.
	opts := &volumeactions.ExtendSizeOpts{
		NewSize: int(req.GetSize()),
	}

	err := volumeactions.ExtendSize(d.blockStoragev2, req.GetId(), opts).ExtractErr()
	if err != nil {
		log.Error("Cannot extend volume:", err)
		return nil, err
	}

	return &model.VolumeSpec{
		BaseModel: &model.BaseModel{
			Id: req.GetId(),
		},
		Name:             req.GetName(),
		Description:      req.GetDescription(),
		Size:             int64(req.GetSize()),
		AvailabilityZone: req.GetAvailabilityZone(),
	}, nil
}

// InitializeConnection
func (d *Driver) InitializeConnection(req *pb.CreateAttachmentOpts) (*model.ConnectionInfo, error) {
	opts := &volumeactions.InitializeConnectionOpts{
		IP:        req.HostInfo.GetIp(),
		Host:      req.HostInfo.GetHost(),
		Initiator: req.HostInfo.GetInitiator(),
		Platform:  req.HostInfo.GetPlatform(),
		OSType:    req.HostInfo.GetOsType(),
		Multipath: &req.MultiPath,
	}

	conn, err := volumeactions.InitializeConnection(d.blockStoragev2, req.GetVolumeId(), opts).Extract()
	if err != nil {
		log.Error("Cannot initialize volume connection:", err)
		return nil, err
	}

	return &model.ConnectionInfo{
		DriverVolumeType: "iscsi",
		ConnectionData:   conn,
	}, nil
}

// TerminateConnection
func (d *Driver) TerminateConnection(opt *pb.DeleteAttachmentOpts) error { return nil }

// CreateSnapshot
func (d *Driver) CreateSnapshot(req *pb.CreateVolumeSnapshotOpts) (*model.VolumeSnapshotSpec, error) {
	opts := &snapshotsv2.CreateOpts{
		VolumeID:    req.GetVolumeId(),
		Name:        req.GetName(),
		Description: req.GetDescription(),
	}

	snp, err := snapshotsv2.Create(d.blockStoragev2, opts).Extract()
	if err != nil {
		log.Error("Cannot create snapshot:", err)
		return nil, err
	}

	// Currently dock framework doesn't support sync data from storage system,
	// therefore, it's necessary to wait for the result of resource's creation.
	// Timout after 10s.
	timeout := time.After(10 * time.Second)
	ticker := time.NewTicker(300 * time.Millisecond)
	done := make(chan bool, 1)
	go func() {
		for {
			select {
			case <-ticker.C:
				tmpSnp, err := d.PullSnapshot(snp.ID)
				if err != nil {
					continue
				}
				if tmpSnp.Status != "creating" {
					snp.Status = tmpSnp.Status
					close(done)
					return
				}
			case <-timeout:
				close(done)
				return
			}

		}
	}()
	<-done

	return &model.VolumeSnapshotSpec{
		BaseModel: &model.BaseModel{
			Id: snp.ID,
		},
		Name:        snp.Name,
		Description: snp.Description,
		Size:        int64(snp.Size),
		Status:      snp.Status,
		VolumeId:    req.GetVolumeId(),
	}, nil
}

// PullSnapshot
func (d *Driver) PullSnapshot(snapID string) (*model.VolumeSnapshotSpec, error) {
	snp, err := snapshotsv2.Get(d.blockStoragev2, snapID).Extract()
	if err != nil {
		log.Error("Cannot get snapshot:", err)
		return nil, err
	}

	return &model.VolumeSnapshotSpec{
		BaseModel: &model.BaseModel{
			Id: snp.ID,
		},
		Name:        snp.Name,
		Description: snp.Description,
		Size:        int64(snp.Size),
		Status:      snp.Status,
		VolumeId:    snp.VolumeID,
	}, nil
}

// DeleteSnapshot
func (d *Driver) DeleteSnapshot(req *pb.DeleteVolumeSnapshotOpts) error {
	if err := snapshotsv2.Delete(d.blockStoragev2, req.GetId()).ExtractErr(); err != nil {
		log.Error("Cannot delete snapshot:", err)
		return err
	}

	return nil
}

// ListPools
func (d *Driver) ListPools() ([]*model.StoragePoolSpec, error) {
	log.Info("Starting list pools in cinder drivers.")
	opts := &schedulerstats.ListOpts{Detail: true}

	pages, err := schedulerstats.List(d.blockStoragev2, opts).AllPages()
	if err != nil {
		log.Error("Cannot list storage pools:", err)
		return nil, err
	}

	polpages, err := schedulerstats.ExtractStoragePools(pages)
	if err != nil {
		log.Error("Cannot extract storage pools:", err)
		return nil, err
	}
	var pols []*model.StoragePoolSpec
	for _, page := range polpages {
		if _, ok := d.conf.Pool[page.Name]; !ok {
			continue
		}

		pol := &model.StoragePoolSpec{
			BaseModel: &model.BaseModel{
				Id: uuid.NewV5(uuid.NamespaceOID, page.Name).String(),
			},
			Name:             page.Name,
			TotalCapacity:    int64(page.Capabilities.TotalCapacityGB),
			FreeCapacity:     int64(page.Capabilities.FreeCapacityGB),
			StorageType:      d.conf.Pool[page.Name].StorageType,
			AvailabilityZone: d.conf.Pool[page.Name].AvailabilityZone,
			Extras:           d.conf.Pool[page.Name].Extras,
		}
		pols = append(pols, pol)
	}
	return pols, nil
}

func (d *Driver) CreateVolumeGroup(opt *pb.CreateVolumeGroupOpts, vg *model.VolumeGroupSpec) (*model.VolumeGroupSpec, error) {
	return nil, &model.NotImplementError{"Method CreateVolumeGroup did not implement."}
}

func (d *Driver) UpdateVolumeGroup(opt *pb.UpdateVolumeGroupOpts, vg *model.VolumeGroupSpec, addVolumesRef []*model.VolumeSpec, removeVolumesRef []*model.VolumeSpec) (*model.VolumeGroupSpec, []*model.VolumeSpec, []*model.VolumeSpec, error) {
	return nil, nil, nil, &model.NotImplementError{"Method UpdateVolumeGroup did not implement."}
}

func (d *Driver) DeleteVolumeGroup(opt *pb.DeleteVolumeGroupOpts, vg *model.VolumeGroupSpec, volumes []*model.VolumeSpec) (*model.VolumeGroupSpec, []*model.VolumeSpec, error) {
	return nil, nil, &model.NotImplementError{"Method UpdateVolumeGroup did not implement."}
}
<commit_msg>Fixed some bugs abount cinder (#400)<commit_after>// Copyright (c) 2017 Huawei Technologies Co., Ltd. All Rights Reserved.
//
// 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.

/*
This module implements cinder driver for OpenSDS. Cinder driver will pass
these operation requests about volume to gophercloud which is an OpenStack
Go SDK.

*/

package cinder

import (
	"time"

	log "github.com/golang/glog"
	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
	"github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/schedulerstats"
	"github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions"
	"github.com/gophercloud/gophercloud/openstack/blockstorage/noauth"
	snapshotsv2 "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/snapshots"
	volumesv2 "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
	. "github.com/opensds/opensds/contrib/drivers/utils/config"
	pb "github.com/opensds/opensds/pkg/dock/proto"
	"github.com/opensds/opensds/pkg/model"
	"github.com/opensds/opensds/pkg/utils/config"
	"github.com/satori/go.uuid"
)

const (
	defaultConfPath = "/etc/opensds/driver/cinder.yaml"
	KCinderVolumeId = "cinderVolumeId"
	KCinderSnapId   = "cinderSnapId"
)

var conf = CinderConfig{}

// Driver is a struct of Cinder backend, which can be called to manage block
// storage service defined in gophercloud.
type Driver struct {
	// Current block storage version
	blockStoragev2 *gophercloud.ServiceClient
	blockStoragev3 *gophercloud.ServiceClient

	conf *CinderConfig
}

// AuthOptions
type AuthOptions struct {
	NoAuth           bool   `yaml:"noAuth,omitempty"`
	CinderEndpoint   string `yaml:"cinderEndpoint,omitempty"`
	IdentityEndpoint string `yaml:"endpoint,omitempty"`
	DomainID         string `yaml:"domainId,omitempty"`
	DomainName       string `yaml:"domainName,omitempty"`
	Username         string `yaml:"username,omitempty"`
	Password         string `yaml:"password,omitempty"`
	TenantID         string `yaml:"tenantId,omitempty"`
	TenantName       string `yaml:"tenantName,omitempty"`
}

// CinderConfig
type CinderConfig struct {
	AuthOptions `yaml:"authOptions"`
	Pool        map[string]PoolProperties `yaml:"pool,flow"`
}

// Setup
func (d *Driver) Setup() error {
	// Read cinder config file
	d.conf = &CinderConfig{}
	p := config.CONF.OsdsDock.Backends.Cinder.ConfigPath
	if "" == p {
		p = defaultConfPath
	}
	Parse(d.conf, p)

	opts := gophercloud.AuthOptions{
		IdentityEndpoint: d.conf.IdentityEndpoint,
		DomainID:         d.conf.DomainID,
		DomainName:       d.conf.DomainName,
		Username:         d.conf.Username,
		Password:         d.conf.Password,
		TenantID:         d.conf.TenantID,
		TenantName:       d.conf.TenantName,
	}

	if d.conf.NoAuth {
		provider, err := noauth.NewClient(opts)
		if err != nil {
			log.Error("When get no authentication options:", err)
			return err
		}

		d.blockStoragev2, err = noauth.NewBlockStorageV2(provider, noauth.EndpointOpts{
			CinderEndpoint: d.conf.CinderEndpoint,
		})
		if err != nil {
			log.Error("When get no authentication block storage session:", err)
			return err
		}
	} else {
		provider, err := openstack.AuthenticatedClient(opts)
		if err != nil {
			log.Error("When get auth options:", err)
			return err
		}

		d.blockStoragev2, err = openstack.NewBlockStorageV2(provider, gophercloud.EndpointOpts{})
		if err != nil {
			log.Error("When get block storage session:", err)
			return err
		}
	}

	return nil
}

// Unset
func (d *Driver) Unset() error { return nil }

// CreateVolume
func (d *Driver) CreateVolume(req *pb.CreateVolumeOpts) (*model.VolumeSpec, error) {
	//Configure create request body.
	opts := &volumesv2.CreateOpts{
		Name:        req.GetName(),
		Description: req.GetDescription(),
		Size:        int(req.GetSize()),
	}

	vol, err := volumesv2.Create(d.blockStoragev2, opts).Extract()
	if err != nil {
		log.Error("Cannot create volume:", err)
		return nil, err
	}

	// Currently dock framework doesn't support sync data from storage system,
	// therefore, it's necessary to wait for the result of resource's creation.
	// Timout after 10s.
	timeout := time.After(10 * time.Second)
	ticker := time.NewTicker(300 * time.Millisecond)
	done := make(chan bool, 1)
	go func() {
		for {
			select {
			case <-ticker.C:
				tmpVol, err := d.PullVolume(vol.ID)
				if err != nil {
					continue
				}
				if tmpVol.Status != "creating" {
					vol.Status = tmpVol.Status
					close(done)
					return
				}
			case <-timeout:
				close(done)
				return
			}

		}
	}()
	<-done

	return &model.VolumeSpec{
		BaseModel: &model.BaseModel{
			Id: req.GetId(),
		},
		Name:             vol.Name,
		Description:      vol.Description,
		Size:             int64(vol.Size),
		AvailabilityZone: req.GetAvailabilityZone(),
		Status:           vol.Status,
		Metadata:         map[string]string{KCinderVolumeId: vol.ID},
	}, nil
}

// PullVolume
func (d *Driver) PullVolume(volID string) (*model.VolumeSpec, error) {
	vol, err := volumesv2.Get(d.blockStoragev2, volID).Extract()
	if err != nil {
		log.Error("Cannot get volume:", err)
		return nil, err
	}

	return &model.VolumeSpec{
		BaseModel: &model.BaseModel{
			Id: volID,
		},
		Name:        vol.Name,
		Description: vol.Description,
		Size:        int64(vol.Size),
		Status:      vol.Status,
	}, nil
}

// DeleteVolume
func (d *Driver) DeleteVolume(req *pb.DeleteVolumeOpts) error {
	cinderVolId := req.Metadata[KCinderVolumeId]
	if err := volumesv2.Delete(d.blockStoragev2, cinderVolId).ExtractErr(); err != nil {
		log.Error("Cannot delete volume:", err)
		return err
	}

	return nil
}

// ExtendVolume ...
func (d *Driver) ExtendVolume(req *pb.ExtendVolumeOpts) (*model.VolumeSpec, error) {
	//Configure create request body.
	opts := &volumeactions.ExtendSizeOpts{
		NewSize: int(req.GetSize()),
	}
	cinderVolId := req.Metadata[KCinderVolumeId]
	err := volumeactions.ExtendSize(d.blockStoragev2, cinderVolId, opts).ExtractErr()
	if err != nil {
		log.Error("Cannot extend volume:", err)
		return nil, err
	}

	return &model.VolumeSpec{
		BaseModel: &model.BaseModel{
			Id: req.GetId(),
		},
		Name:             req.GetName(),
		Description:      req.GetDescription(),
		Size:             int64(req.GetSize()),
		AvailabilityZone: req.GetAvailabilityZone(),
	}, nil
}

// InitializeConnection
func (d *Driver) InitializeConnection(req *pb.CreateAttachmentOpts) (*model.ConnectionInfo, error) {
	opts := &volumeactions.InitializeConnectionOpts{
		IP:        req.HostInfo.GetIp(),
		Host:      req.HostInfo.GetHost(),
		Initiator: req.HostInfo.GetInitiator(),
		Platform:  req.HostInfo.GetPlatform(),
		OSType:    req.HostInfo.GetOsType(),
		Multipath: &req.MultiPath,
	}

	cinderVolId := req.Metadata[KCinderVolumeId]
	conn, err := volumeactions.InitializeConnection(d.blockStoragev2, cinderVolId, opts).Extract()
	if err != nil {
		log.Error("Cannot initialize volume connection:", err)
		return nil, err
	}

	data := conn["data"].(map[string]interface{})
	connData := map[string]interface{}{
		"accessMode":       data["access_mode"],
		"targetDiscovered": data["target_discovered"],
		"targetIQN":        data["target_iqn"],
		"targetPortal":     data["target_portal"],
		"discard":          false,
		"targetLun":        data["target_lun"],
	}
	// If auth is enabled, add auth info.
	if authMethod, ok := data["auth_method"]; ok {
		connData["authMethod"] = authMethod
		connData["authPassword"] = data["auth_password"]
		connData["authUsername"] = data["auth_username"]
	}
	return &model.ConnectionInfo{
		DriverVolumeType: "iscsi",
		ConnectionData:   connData,
	}, nil
}

// TerminateConnection
func (d *Driver) TerminateConnection(req *pb.DeleteAttachmentOpts) error {
	opts := volumeactions.TerminateConnectionOpts{
		IP:        req.HostInfo.GetIp(),
		Host:      req.HostInfo.GetHost(),
		Initiator: req.HostInfo.GetInitiator(),
		Platform:  req.HostInfo.GetPlatform(),
		OSType:    req.HostInfo.GetOsType(),
	}
	cinderVolId := req.Metadata[KCinderVolumeId]
	return volumeactions.TerminateConnection(d.blockStoragev2, cinderVolId, opts).ExtractErr()
}

// CreateSnapshot
func (d *Driver) CreateSnapshot(req *pb.CreateVolumeSnapshotOpts) (*model.VolumeSnapshotSpec, error) {
	cinderVolId := req.Metadata[KCinderVolumeId]
	opts := &snapshotsv2.CreateOpts{
		VolumeID:    cinderVolId,
		Name:        req.GetName(),
		Description: req.GetDescription(),
	}

	snp, err := snapshotsv2.Create(d.blockStoragev2, opts).Extract()
	if err != nil {
		log.Error("Cannot create snapshot:", err)
		return nil, err
	}

	// Currently dock framework doesn't support sync data from storage system,
	// therefore, it's necessary to wait for the result of resource's creation.
	// Timout after 10s.
	timeout := time.After(10 * time.Second)
	ticker := time.NewTicker(300 * time.Millisecond)
	done := make(chan bool, 1)
	go func() {
		for {
			select {
			case <-ticker.C:
				tmpSnp, err := d.PullSnapshot(snp.ID)
				if err != nil {
					continue
				}
				if tmpSnp.Status != "creating" {
					snp.Status = tmpSnp.Status
					close(done)
					return
				}
			case <-timeout:
				close(done)
				return
			}

		}
	}()
	<-done

	return &model.VolumeSnapshotSpec{
		BaseModel: &model.BaseModel{
			Id: req.GetId(),
		},
		Name:        snp.Name,
		Description: snp.Description,
		Size:        int64(snp.Size),
		Status:      snp.Status,
		VolumeId:    req.GetVolumeId(),
		Metadata:    map[string]string{KCinderSnapId: snp.ID},
	}, nil
}

// PullSnapshot
func (d *Driver) PullSnapshot(snapID string) (*model.VolumeSnapshotSpec, error) {
	snp, err := snapshotsv2.Get(d.blockStoragev2, snapID).Extract()
	if err != nil {
		log.Error("Cannot get snapshot:", err)
		return nil, err
	}

	return &model.VolumeSnapshotSpec{
		BaseModel: &model.BaseModel{
			Id: snapID,
		},
		Name:        snp.Name,
		Description: snp.Description,
		Size:        int64(snp.Size),
		Status:      snp.Status,
	}, nil
}

// DeleteSnapshot
func (d *Driver) DeleteSnapshot(req *pb.DeleteVolumeSnapshotOpts) error {
	cinderSnapId := req.Metadata[KCinderSnapId]
	if err := snapshotsv2.Delete(d.blockStoragev2, cinderSnapId).ExtractErr(); err != nil {
		log.Error("Cannot delete snapshot:", err)
		return err
	}
	return nil
}

// ListPools
func (d *Driver) ListPools() ([]*model.StoragePoolSpec, error) {
	log.Info("Starting list pools in cinder drivers.")
	opts := &schedulerstats.ListOpts{Detail: true}

	pages, err := schedulerstats.List(d.blockStoragev2, opts).AllPages()
	if err != nil {
		log.Error("Cannot list storage pools:", err)
		return nil, err
	}

	polpages, err := schedulerstats.ExtractStoragePools(pages)
	if err != nil {
		log.Error("Cannot extract storage pools:", err)
		return nil, err
	}
	var pols []*model.StoragePoolSpec
	for _, page := range polpages {
		if _, ok := d.conf.Pool[page.Name]; !ok {
			continue
		}

		pol := &model.StoragePoolSpec{
			BaseModel: &model.BaseModel{
				Id: uuid.NewV5(uuid.NamespaceOID, page.Name).String(),
			},
			Name:             page.Name,
			TotalCapacity:    int64(page.Capabilities.TotalCapacityGB),
			FreeCapacity:     int64(page.Capabilities.FreeCapacityGB),
			StorageType:      d.conf.Pool[page.Name].StorageType,
			AvailabilityZone: d.conf.Pool[page.Name].AvailabilityZone,
			Extras:           d.conf.Pool[page.Name].Extras,
		}
		pols = append(pols, pol)
	}
	return pols, nil
}

func (d *Driver) CreateVolumeGroup(req *pb.CreateVolumeGroupOpts, vg *model.VolumeGroupSpec) (*model.VolumeGroupSpec, error) {
	return nil, &model.NotImplementError{"Method CreateVolumeGroup did not implement."}
}

func (d *Driver) UpdateVolumeGroup(req *pb.UpdateVolumeGroupOpts, vg *model.VolumeGroupSpec, addVolumesRef []*model.VolumeSpec, removeVolumesRef []*model.VolumeSpec) (*model.VolumeGroupSpec, []*model.VolumeSpec, []*model.VolumeSpec, error) {
	return nil, nil, nil, &model.NotImplementError{"Method UpdateVolumeGroup did not implement."}
}

func (d *Driver) DeleteVolumeGroup(req *pb.DeleteVolumeGroupOpts, vg *model.VolumeGroupSpec, volumes []*model.VolumeSpec) (*model.VolumeGroupSpec, []*model.VolumeSpec, error) {
	return nil, nil, &model.NotImplementError{"Method UpdateVolumeGroup did not implement."}
}
<|endoftext|>


<commit_before>// Package libstring provides string related library functions.
package libstring

import (
	"crypto/rand"
	"encoding/base64"
	"os"
	"os/user"
	"strings"
)

// ExpandTilde is a convenience function that expands ~ to full path.
func ExpandTilde(path string) string {
	usr, _ := user.Current()
	homeDir := usr.HomeDir

	if path[:2] == "~/" {
		path = strings.Replace(path, "~", homeDir, 1)
	}
	return path
}

// ExpandTilde is a convenience function that expands both ~ and $ENV.
func ExpandTildeAndEnv(path string) string {
	path = ExpandTilde(path)
	return os.ExpandEnv(path)
}

// GeneratePassword returns password.
// size determines length of initial seed bytes.
func GeneratePassword(size int) (string, error) {
	// Force minimum size to 32
	if size < 32 {
		size = 32
	}

	rb := make([]byte, size)
	_, err := rand.Read(rb)

	if err != nil {
		return "", err
	}

	return base64.URLEncoding.EncodeToString(rb), nil
}
<commit_msg>There is a situation where user.Current() returns error.<commit_after>// Package libstring provides string related library functions.
package libstring

import (
	"crypto/rand"
	"encoding/base64"
	"os"
	"os/user"
	"strings"
)

// ExpandTilde is a convenience function that expands ~ to full path.
func ExpandTilde(path string) string {
	usr, err := user.Current()
	if err != nil || usr == nil {
		return path
	}

	homeDir := usr.HomeDir

	if path[:2] == "~/" {
		path = strings.Replace(path, "~", homeDir, 1)
	}
	return path
}

// ExpandTilde is a convenience function that expands both ~ and $ENV.
func ExpandTildeAndEnv(path string) string {
	path = ExpandTilde(path)
	return os.ExpandEnv(path)
}

// GeneratePassword returns password.
// size determines length of initial seed bytes.
func GeneratePassword(size int) (string, error) {
	// Force minimum size to 32
	if size < 32 {
		size = 32
	}

	rb := make([]byte, size)
	_, err := rand.Read(rb)

	if err != nil {
		return "", err
	}

	return base64.URLEncoding.EncodeToString(rb), nil
}
<|endoftext|>