<commit_before>package vfs

import (
	"io"
	"os"
	"sync"
	"time"

	"github.com/ncw/rclone/fs"
	"github.com/ncw/rclone/fs/operations"
)

// WriteFileHandle is an open for write handle on a File
type WriteFileHandle struct {
	baseHandle
	mu          sync.Mutex
	closed      bool // set if handle has been closed
	remote      string
	pipeWriter  *io.PipeWriter
	o           fs.Object
	result      chan error
	file        *File
	writeCalled bool // set the first time Write() is called
	offset      int64
	opened      bool
	flags       int
	truncated   bool
}

// Check interfaces
var (
	_ io.Writer   = (*WriteFileHandle)(nil)
	_ io.WriterAt = (*WriteFileHandle)(nil)
	_ io.Closer   = (*WriteFileHandle)(nil)
)

func newWriteFileHandle(d *Dir, f *File, remote string, flags int) (*WriteFileHandle, error) {
	fh := &WriteFileHandle{
		remote: remote,
		flags:  flags,
		result: make(chan error, 1),
		file:   f,
	}
	fh.file.addWriter(fh)
	return fh, nil
}

// returns whether it is OK to truncate the file
func (fh *WriteFileHandle) safeToTruncate() bool {
	return fh.truncated || fh.flags&os.O_TRUNC != 0 || !fh.file.exists()
}

// openPending opens the file if there is a pending open
//
// call with the lock held
func (fh *WriteFileHandle) openPending() (err error) {
	if fh.opened {
		return nil
	}
	if !fh.safeToTruncate() {
		fs.Errorf(fh.remote, "WriteFileHandle: Can't open for write without O_TRUNC on existing file without --vfs-cache-mode >= writes")
		return EPERM
	}
	var pipeReader *io.PipeReader
	pipeReader, fh.pipeWriter = io.Pipe()
	go func() {
		// NB Rcat deals with Stats.Transferring etc
		o, err := operations.Rcat(fh.file.d.f, fh.remote, pipeReader, time.Now())
		if err != nil {
			fs.Errorf(fh.remote, "WriteFileHandle.New Rcat failed: %v", err)
		}
		// Close the pipeReader so the pipeWriter fails with ErrClosedPipe
		_ = pipeReader.Close()
		fh.o = o
		fh.result <- err
	}()
	fh.file.setSize(0)
	fh.truncated = true
	fh.file.d.addObject(fh.file) // make sure the directory has this object in it now
	fh.opened = true
	return nil
}

// String converts it to printable
func (fh *WriteFileHandle) String() string {
	if fh == nil {
		return "<nil *WriteFileHandle>"
	}
	fh.mu.Lock()
	defer fh.mu.Unlock()
	if fh.file == nil {
		return "<nil *WriteFileHandle.file>"
	}
	return fh.file.String() + " (w)"
}

// Node returns the Node assocuated with this - satisfies Noder interface
func (fh *WriteFileHandle) Node() Node {
	fh.mu.Lock()
	defer fh.mu.Unlock()
	return fh.file
}

// WriteAt writes len(p) bytes from p to the underlying data stream at offset
// off. It returns the number of bytes written from p (0 <= n <= len(p)) and
// any error encountered that caused the write to stop early. WriteAt must
// return a non-nil error if it returns n < len(p).
//
// If WriteAt is writing to a destination with a seek offset, WriteAt should
// not affect nor be affected by the underlying seek offset.
//
// Clients of WriteAt can execute parallel WriteAt calls on the same
// destination if the ranges do not overlap.
//
// Implementations must not retain p.
func (fh *WriteFileHandle) WriteAt(p []byte, off int64) (n int, err error) {
	fh.mu.Lock()
	defer fh.mu.Unlock()
	return fh.writeAt(p, off)
}

// Implementatino of WriteAt - call with lock held
func (fh *WriteFileHandle) writeAt(p []byte, off int64) (n int, err error) {
	// fs.Debugf(fh.remote, "WriteFileHandle.Write len=%d", len(p))
	if fh.closed {
		fs.Errorf(fh.remote, "WriteFileHandle.Write: error: %v", EBADF)
		return 0, ECLOSED
	}
	if fh.offset != off {
		fs.Errorf(fh.remote, "WriteFileHandle.Write: can't seek in file without --vfs-cache-mode >= writes")
		return 0, ESPIPE
	}
	if err = fh.openPending(); err != nil {
		return 0, err
	}
	fh.writeCalled = true
	n, err = fh.pipeWriter.Write(p)
	fh.offset += int64(n)
	fh.file.setSize(fh.offset)
	if err != nil {
		fs.Errorf(fh.remote, "WriteFileHandle.Write error: %v", err)
		return 0, err
	}
	// fs.Debugf(fh.remote, "WriteFileHandle.Write OK (%d bytes written)", n)
	return n, nil
}

// Write writes len(p) bytes from p to the underlying data stream. It returns
// the number of bytes written from p (0 <= n <= len(p)) and any error
// encountered that caused the write to stop early. Write must return a non-nil
// error if it returns n < len(p). Write must not modify the slice data, even
// temporarily.
//
// Implementations must not retain p.
func (fh *WriteFileHandle) Write(p []byte) (n int, err error) {
	fh.mu.Lock()
	defer fh.mu.Unlock()
	// Since we can't seek, just call WriteAt with the current offset
	return fh.writeAt(p, fh.offset)
}

// WriteString a string to the file
func (fh *WriteFileHandle) WriteString(s string) (n int, err error) {
	return fh.Write([]byte(s))
}

// Offset returns the offset of the file pointer
func (fh *WriteFileHandle) Offset() (offset int64) {
	fh.mu.Lock()
	defer fh.mu.Unlock()
	return fh.offset
}

// close the file handle returning EBADF if it has been
// closed already.
//
// Must be called with fh.mu held
func (fh *WriteFileHandle) close() (err error) {
	if fh.closed {
		return ECLOSED
	}
	fh.closed = true
	// leave writer open until file is transferred
	defer func() {
		fh.file.delWriter(fh, false)
		fh.file.finishWriterClose()
	}()
	// If file not opened and not safe to truncate then then leave file intact
	if !fh.opened && !fh.safeToTruncate() {
		return nil
	}
	if err = fh.openPending(); err != nil {
		return err
	}
	writeCloseErr := fh.pipeWriter.Close()
	err = <-fh.result
	if err == nil {
		fh.file.setObject(fh.o)
		err = writeCloseErr
	}
	return err
}

// Close closes the file
func (fh *WriteFileHandle) Close() error {
	fh.mu.Lock()
	defer fh.mu.Unlock()
	return fh.close()
}

// Flush is called on each close() of a file descriptor. So if a
// filesystem wants to return write errors in close() and the file has
// cached dirty data, this is a good place to write back data and
// return any errors. Since many applications ignore close() errors
// this is not always useful.
//
// NOTE: The flush() method may be called more than once for each
// open(). This happens if more than one file descriptor refers to an
// opened file due to dup(), dup2() or fork() calls. It is not
// possible to determine if a flush is final, so each flush should be
// treated equally. Multiple write-flush sequences are relatively
// rare, so this shouldn't be a problem.
//
// Filesystems shouldn't assume that flush will always be called after
// some writes, or that if will be called at all.
func (fh *WriteFileHandle) Flush() error {
	fh.mu.Lock()
	defer fh.mu.Unlock()
	if fh.closed {
		fs.Debugf(fh.remote, "WriteFileHandle.Flush nothing to do")
		return nil
	}
	// fs.Debugf(fh.remote, "WriteFileHandle.Flush")
	// If Write hasn't been called then ignore the Flush - Release
	// will pick it up
	if !fh.writeCalled {
		fs.Debugf(fh.remote, "WriteFileHandle.Flush unwritten handle, writing 0 bytes to avoid race conditions")
		_, err := fh.writeAt([]byte{}, fh.offset)
		return err
	}
	err := fh.close()
	if err != nil {
		fs.Errorf(fh.remote, "WriteFileHandle.Flush error: %v", err)
	} else {
		// fs.Debugf(fh.remote, "WriteFileHandle.Flush OK")
	}
	return err
}

// Release is called when we are finished with the file handle
//
// It isn't called directly from userspace so the error is ignored by
// the kernel
func (fh *WriteFileHandle) Release() error {
	fh.mu.Lock()
	defer fh.mu.Unlock()
	if fh.closed {
		fs.Debugf(fh.remote, "WriteFileHandle.Release nothing to do")
		return nil
	}
	fs.Debugf(fh.remote, "WriteFileHandle.Release closing")
	err := fh.close()
	if err != nil {
		fs.Errorf(fh.remote, "WriteFileHandle.Release error: %v", err)
	} else {
		// fs.Debugf(fh.remote, "WriteFileHandle.Release OK")
	}
	return err
}

// Stat returns info about the file
func (fh *WriteFileHandle) Stat() (os.FileInfo, error) {
	fh.mu.Lock()
	defer fh.mu.Unlock()
	return fh.file, nil
}

// Truncate file to given size
func (fh *WriteFileHandle) Truncate(size int64) (err error) {
	fh.mu.Lock()
	defer fh.mu.Unlock()
	if fh.closed {
		return ECLOSED
	}
	if size != fh.offset {
		fs.Errorf(fh.remote, "WriteFileHandle: Truncate: Can't change size without --vfs-cache-mode >= writes")
		return EPERM
	}
	// File is correct size
	if size == 0 {
		fh.truncated = true
	}
	return nil
}

// Read reads up to len(p) bytes into p.
func (fh *WriteFileHandle) Read(p []byte) (n int, err error) {
	fs.Errorf(fh.remote, "WriteFileHandle: Read: Can't read and write to file without --vfs-cache-mode >= minimal")
	return 0, EPERM
}

// ReadAt reads len(p) bytes into p starting at offset off in the
// underlying input source. It returns the number of bytes read (0 <=
// n <= len(p)) and any error encountered.
func (fh *WriteFileHandle) ReadAt(p []byte, off int64) (n int, err error) {
	fs.Errorf(fh.remote, "WriteFileHandle: ReadAt: Can't read and write to file without --vfs-cache-mode >= minimal")
	return 0, EPERM
}

// Sync commits the current contents of the file to stable storage. Typically,
// this means flushing the file system's in-memory copy of recently written
// data to disk.
func (fh *WriteFileHandle) Sync() error {
	return nil
}
<commit_msg>vfs: make WriteAt for non cached files work with non-sequential writes<commit_after>package vfs

import (
	"io"
	"os"
	"sync"
	"time"

	"github.com/ncw/rclone/fs"
	"github.com/ncw/rclone/fs/operations"
)

// WriteFileHandle is an open for write handle on a File
type WriteFileHandle struct {
	baseHandle
	mu          sync.Mutex
	closed      bool // set if handle has been closed
	remote      string
	pipeWriter  *io.PipeWriter
	o           fs.Object
	result      chan error
	file        *File
	writeCalled bool // set the first time Write() is called
	offset      int64
	opened      bool
	flags       int
	truncated   bool
}

// Check interfaces
var (
	_ io.Writer   = (*WriteFileHandle)(nil)
	_ io.WriterAt = (*WriteFileHandle)(nil)
	_ io.Closer   = (*WriteFileHandle)(nil)
)

func newWriteFileHandle(d *Dir, f *File, remote string, flags int) (*WriteFileHandle, error) {
	fh := &WriteFileHandle{
		remote: remote,
		flags:  flags,
		result: make(chan error, 1),
		file:   f,
	}
	fh.file.addWriter(fh)
	return fh, nil
}

// returns whether it is OK to truncate the file
func (fh *WriteFileHandle) safeToTruncate() bool {
	return fh.truncated || fh.flags&os.O_TRUNC != 0 || !fh.file.exists()
}

// openPending opens the file if there is a pending open
//
// call with the lock held
func (fh *WriteFileHandle) openPending() (err error) {
	if fh.opened {
		return nil
	}
	if !fh.safeToTruncate() {
		fs.Errorf(fh.remote, "WriteFileHandle: Can't open for write without O_TRUNC on existing file without --vfs-cache-mode >= writes")
		return EPERM
	}
	var pipeReader *io.PipeReader
	pipeReader, fh.pipeWriter = io.Pipe()
	go func() {
		// NB Rcat deals with Stats.Transferring etc
		o, err := operations.Rcat(fh.file.d.f, fh.remote, pipeReader, time.Now())
		if err != nil {
			fs.Errorf(fh.remote, "WriteFileHandle.New Rcat failed: %v", err)
		}
		// Close the pipeReader so the pipeWriter fails with ErrClosedPipe
		_ = pipeReader.Close()
		fh.o = o
		fh.result <- err
	}()
	fh.file.setSize(0)
	fh.truncated = true
	fh.file.d.addObject(fh.file) // make sure the directory has this object in it now
	fh.opened = true
	return nil
}

// String converts it to printable
func (fh *WriteFileHandle) String() string {
	if fh == nil {
		return "<nil *WriteFileHandle>"
	}
	fh.mu.Lock()
	defer fh.mu.Unlock()
	if fh.file == nil {
		return "<nil *WriteFileHandle.file>"
	}
	return fh.file.String() + " (w)"
}

// Node returns the Node assocuated with this - satisfies Noder interface
func (fh *WriteFileHandle) Node() Node {
	fh.mu.Lock()
	defer fh.mu.Unlock()
	return fh.file
}

// WriteAt writes len(p) bytes from p to the underlying data stream at offset
// off. It returns the number of bytes written from p (0 <= n <= len(p)) and
// any error encountered that caused the write to stop early. WriteAt must
// return a non-nil error if it returns n < len(p).
//
// If WriteAt is writing to a destination with a seek offset, WriteAt should
// not affect nor be affected by the underlying seek offset.
//
// Clients of WriteAt can execute parallel WriteAt calls on the same
// destination if the ranges do not overlap.
//
// Implementations must not retain p.
func (fh *WriteFileHandle) WriteAt(p []byte, off int64) (n int, err error) {
	fh.mu.Lock()
	defer fh.mu.Unlock()
	return fh.writeAt(p, off)
}

// Implementatino of WriteAt - call with lock held
func (fh *WriteFileHandle) writeAt(p []byte, off int64) (n int, err error) {
	// defer log.Trace(fh.remote, "len=%d off=%d", len(p), off)("n=%d, fh.off=%d, err=%v", &n, &fh.offset, &err)
	if fh.closed {
		fs.Errorf(fh.remote, "WriteFileHandle.Write: error: %v", EBADF)
		return 0, ECLOSED
	}
	// Wait a short time for sequential writes to appear
	const maxTries = 1000
	const sleepTime = 1 * time.Millisecond
	for try := 1; fh.offset != off && try <= maxTries; try++ {
		//fs.Debugf(fh.remote, "waiting for in sequence write %d/%d", try, maxTries)
		fh.mu.Unlock()
		time.Sleep(sleepTime)
		fh.mu.Lock()
	}
	if fh.offset != off {
		fs.Errorf(fh.remote, "WriteFileHandle.Write: can't seek in file without --vfs-cache-mode >= writes")
		return 0, ESPIPE
	}
	if err = fh.openPending(); err != nil {
		return 0, err
	}
	fh.writeCalled = true
	n, err = fh.pipeWriter.Write(p)
	fh.offset += int64(n)
	fh.file.setSize(fh.offset)
	if err != nil {
		fs.Errorf(fh.remote, "WriteFileHandle.Write error: %v", err)
		return 0, err
	}
	// fs.Debugf(fh.remote, "WriteFileHandle.Write OK (%d bytes written)", n)
	return n, nil
}

// Write writes len(p) bytes from p to the underlying data stream. It returns
// the number of bytes written from p (0 <= n <= len(p)) and any error
// encountered that caused the write to stop early. Write must return a non-nil
// error if it returns n < len(p). Write must not modify the slice data, even
// temporarily.
//
// Implementations must not retain p.
func (fh *WriteFileHandle) Write(p []byte) (n int, err error) {
	fh.mu.Lock()
	defer fh.mu.Unlock()
	// Since we can't seek, just call WriteAt with the current offset
	return fh.writeAt(p, fh.offset)
}

// WriteString a string to the file
func (fh *WriteFileHandle) WriteString(s string) (n int, err error) {
	return fh.Write([]byte(s))
}

// Offset returns the offset of the file pointer
func (fh *WriteFileHandle) Offset() (offset int64) {
	fh.mu.Lock()
	defer fh.mu.Unlock()
	return fh.offset
}

// close the file handle returning EBADF if it has been
// closed already.
//
// Must be called with fh.mu held
func (fh *WriteFileHandle) close() (err error) {
	if fh.closed {
		return ECLOSED
	}
	fh.closed = true
	// leave writer open until file is transferred
	defer func() {
		fh.file.delWriter(fh, false)
		fh.file.finishWriterClose()
	}()
	// If file not opened and not safe to truncate then then leave file intact
	if !fh.opened && !fh.safeToTruncate() {
		return nil
	}
	if err = fh.openPending(); err != nil {
		return err
	}
	writeCloseErr := fh.pipeWriter.Close()
	err = <-fh.result
	if err == nil {
		fh.file.setObject(fh.o)
		err = writeCloseErr
	}
	return err
}

// Close closes the file
func (fh *WriteFileHandle) Close() error {
	fh.mu.Lock()
	defer fh.mu.Unlock()
	return fh.close()
}

// Flush is called on each close() of a file descriptor. So if a
// filesystem wants to return write errors in close() and the file has
// cached dirty data, this is a good place to write back data and
// return any errors. Since many applications ignore close() errors
// this is not always useful.
//
// NOTE: The flush() method may be called more than once for each
// open(). This happens if more than one file descriptor refers to an
// opened file due to dup(), dup2() or fork() calls. It is not
// possible to determine if a flush is final, so each flush should be
// treated equally. Multiple write-flush sequences are relatively
// rare, so this shouldn't be a problem.
//
// Filesystems shouldn't assume that flush will always be called after
// some writes, or that if will be called at all.
func (fh *WriteFileHandle) Flush() error {
	fh.mu.Lock()
	defer fh.mu.Unlock()
	if fh.closed {
		fs.Debugf(fh.remote, "WriteFileHandle.Flush nothing to do")
		return nil
	}
	// fs.Debugf(fh.remote, "WriteFileHandle.Flush")
	// If Write hasn't been called then ignore the Flush - Release
	// will pick it up
	if !fh.writeCalled {
		fs.Debugf(fh.remote, "WriteFileHandle.Flush unwritten handle, writing 0 bytes to avoid race conditions")
		_, err := fh.writeAt([]byte{}, fh.offset)
		return err
	}
	err := fh.close()
	if err != nil {
		fs.Errorf(fh.remote, "WriteFileHandle.Flush error: %v", err)
	} else {
		// fs.Debugf(fh.remote, "WriteFileHandle.Flush OK")
	}
	return err
}

// Release is called when we are finished with the file handle
//
// It isn't called directly from userspace so the error is ignored by
// the kernel
func (fh *WriteFileHandle) Release() error {
	fh.mu.Lock()
	defer fh.mu.Unlock()
	if fh.closed {
		fs.Debugf(fh.remote, "WriteFileHandle.Release nothing to do")
		return nil
	}
	fs.Debugf(fh.remote, "WriteFileHandle.Release closing")
	err := fh.close()
	if err != nil {
		fs.Errorf(fh.remote, "WriteFileHandle.Release error: %v", err)
	} else {
		// fs.Debugf(fh.remote, "WriteFileHandle.Release OK")
	}
	return err
}

// Stat returns info about the file
func (fh *WriteFileHandle) Stat() (os.FileInfo, error) {
	fh.mu.Lock()
	defer fh.mu.Unlock()
	return fh.file, nil
}

// Truncate file to given size
func (fh *WriteFileHandle) Truncate(size int64) (err error) {
	fh.mu.Lock()
	defer fh.mu.Unlock()
	if fh.closed {
		return ECLOSED
	}
	if size != fh.offset {
		fs.Errorf(fh.remote, "WriteFileHandle: Truncate: Can't change size without --vfs-cache-mode >= writes")
		return EPERM
	}
	// File is correct size
	if size == 0 {
		fh.truncated = true
	}
	return nil
}

// Read reads up to len(p) bytes into p.
func (fh *WriteFileHandle) Read(p []byte) (n int, err error) {
	fs.Errorf(fh.remote, "WriteFileHandle: Read: Can't read and write to file without --vfs-cache-mode >= minimal")
	return 0, EPERM
}

// ReadAt reads len(p) bytes into p starting at offset off in the
// underlying input source. It returns the number of bytes read (0 <=
// n <= len(p)) and any error encountered.
func (fh *WriteFileHandle) ReadAt(p []byte, off int64) (n int, err error) {
	fs.Errorf(fh.remote, "WriteFileHandle: ReadAt: Can't read and write to file without --vfs-cache-mode >= minimal")
	return 0, EPERM
}

// Sync commits the current contents of the file to stable storage. Typically,
// this means flushing the file system's in-memory copy of recently written
// data to disk.
func (fh *WriteFileHandle) Sync() error {
	return nil
}
<|endoftext|>


<commit_before>// Copyright Project Harbor 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 handler

// TODO move this file out of v2.0 folder as this is common for all versions of API

import (
	"context"

	"github.com/go-openapi/runtime/middleware"
	errs "github.com/goharbor/harbor/src/server/error"
)

// BaseAPI base API handler
type BaseAPI struct{}

// Prepare default prepare for operation
func (*BaseAPI) Prepare(ctx context.Context, operation string, params interface{}) middleware.Responder {
	return nil
}

// SendError returns response for the err
func (*BaseAPI) SendError(ctx context.Context, err error) middleware.Responder {
	return errs.NewErrResponder(err)
}
<commit_msg>feat(api,permission): add HasPermission, HasProjectPermission in BaseAPI (#10618)<commit_after>// Copyright Project Harbor 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 handler

// TODO move this file out of v2.0 folder as this is common for all versions of API

import (
	"context"

	"github.com/go-openapi/runtime/middleware"
	"github.com/goharbor/harbor/src/common/rbac"
	"github.com/goharbor/harbor/src/common/security"
	"github.com/goharbor/harbor/src/common/utils"
	"github.com/goharbor/harbor/src/common/utils/log"
	"github.com/goharbor/harbor/src/pkg/project"
	errs "github.com/goharbor/harbor/src/server/error"
)

// BaseAPI base API handler
type BaseAPI struct{}

// Prepare default prepare for operation
func (*BaseAPI) Prepare(ctx context.Context, operation string, params interface{}) middleware.Responder {
	return nil
}

// SendError returns response for the err
func (*BaseAPI) SendError(ctx context.Context, err error) middleware.Responder {
	return errs.NewErrResponder(err)
}

// HasPermission returns true when the request has action permission on resource
func (*BaseAPI) HasPermission(ctx context.Context, action rbac.Action, resource rbac.Resource) bool {
	s, ok := security.FromContext(ctx)
	if !ok {
		log.Warningf("security not found in the contxt")
		return false
	}

	return s.Can(action, resource)
}

// HasProjectPermission returns true when the request has action permission on project subresource
func (b *BaseAPI) HasProjectPermission(ctx context.Context, projectIDOrName interface{}, action rbac.Action, subresource ...rbac.Resource) bool {
	projectID, projectName, err := utils.ParseProjectIDOrName(projectIDOrName)
	if err != nil {
		return false
	}

	if projectName != "" {
		// TODO: use the project controller to replace the project manager
		p, err := project.Mgr.Get(projectName)
		if err != nil {
			log.Errorf("failed to get project %s: %v", projectName, err)
			return false
		}
		if p == nil {
			log.Warningf("project %s not found", projectName)
			return false
		}

		projectID = p.ProjectID
	}

	resource := rbac.NewProjectNamespace(projectID).Resource(subresource...)
	return b.HasPermission(ctx, action, resource)
}
<|endoftext|>


<commit_before>/*
 *    Copyright (C) 2015 Stefan Luecke
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Affero General Public License as published
 *    by the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *    Authors: Stefan Luecke <glaxx@glaxx.net>
 */

package webservice

import (
	log "github.com/Sirupsen/logrus"
	"github.com/emicklei/go-restful"
	//"html/template"
	"net/http"
	//"strconv"
	//"strings"
	db "github.com/openlab-aux/lsmsd/database"
)

type UserWebService struct {
	d *db.UserDBProvider
	S *restful.WebService
	a *BasicAuthService
}

func NewUserService(d *db.UserDBProvider, a *BasicAuthService) *UserWebService {
	res := new(UserWebService)
	res.d = d
	res.a = a
	service := new(restful.WebService)
	service.
		Path("/user").
		Doc("User related services").
		ApiVersion("0.1").
		Consumes(restful.MIME_JSON).
		Produces(restful.MIME_JSON)

	service.Route(service.GET("/{name}").
		Param(restful.PathParameter("name", "User identifier")).
		Doc("Returns public accessable information about the referenced user").
		To(res.GetUserByName).
		Writes(db.User{}).
		Do(returnsInternalServerError, returnsNotFound, returnsBadRequest))

	service.Route(service.GET("/{name}/log").
		Param(restful.PathParameter("name", "User identifier")).
		Doc("Returns all changes the user has made in the DB").
		To(res.GetUserLogByName).
		Writes(db.UserActionHistory{}).
		Do(returnsInternalServerError, returnsNotFound, returnsBadRequest))

	service.Route(service.GET("").
		Doc("List all users (this may be replaced by a paginated version)").
		To(res.ListUser).
		Writes([]db.User{}).
		Do(returnsInternalServerError))

	service.Route(service.PUT("").
		Filter(res.a.Auth).
		Doc("Update user information").
		To(res.UpdateUser).
		Reads(db.User{}).
		Do(returnsInternalServerError, returnsNotFound, returnsUpdateSuccessful, returnsBadRequest))

	service.Route(service.POST("").
		Doc("Register a new user").
		To(res.CreateUser).
		Reads(db.User{}).
		Returns(http.StatusOK, "Insert successful", "/user/{name}").
		Returns(http.StatusUnauthorized, "This username is not available", nil).
		Do(returnsInternalServerError, returnsBadRequest))

	service.Route(service.DELETE("/{name}").
		Filter(res.a.Auth).
		Param(restful.PathParameter("name", "User identifier")).
		Doc("Delete a user").
		To(res.DeleteUser).
		Do(returnsInternalServerError, returnsNotFound, returnsDeleteSuccessful, returnsBadRequest))
	res.S = service
	return res
}

func (p *UserWebService) GetUserByName(request *restful.Request, response *restful.Response) {
	name := request.PathParameter("name")
	user, err := p.d.GetUserByName(name)
	if err != nil {
		response.WriteErrorString(http.StatusBadRequest, ERROR_INVALID_ID)
		log.WithFields(log.Fields{"Error Msg": err}).
			Info(ERROR_INVALID_ID)
		return
	}
	response.WriteEntity(user)
}

func (p *UserWebService) GetUserLogByName(request *restful.Request, response *restful.Response) {
	name := request.PathParameter("name")
	ul, err := p.d.GetUserLogByName(name)
	if err != nil {
		response.WriteErrorString(http.StatusNotFound, ERROR_INVALID_ID)
		log.Info(err)
		return
	}
	response.WriteEntity(ul)
	return
}

func (p *UserWebService) ListUser(request *restful.Request, response *restful.Response) {
	usr, err := p.d.ListUser()
	if err != nil {
		response.WriteErrorString(http.StatusInternalServerError, ERROR_INTERNAL)
		log.WithFields(log.Fields{"Error Msg": err}).Warn(ERROR_INTERNAL)
		return
	}
	response.WriteEntity(usr)
}

func (p *UserWebService) UpdateUser(request *restful.Request, response *restful.Response) {
	usr := new(db.User)
	err := request.ReadEntity(usr)
	if err != nil {
		response.WriteErrorString(http.StatusBadRequest, ERROR_INVALID_INPUT)
		log.WithFields(log.Fields{"Error Msg": err}).Warn(ERROR_INVALID_INPUT)
		return
	}

	ex := p.d.CheckUserExistance(usr)
	if ex {
		if usr.Password != "" {
			log.Debug("User supplied new password.")
			err = usr.Secret.SetPassword(usr.Password)
		} else {
			temp, err := p.d.GetUserByName(usr.Name)
			if err != nil {
			} else {
				usr.Secret = temp.Secret // if no new password will be set, preserve old
			}
		}
		if err != nil { //fall through to error handling
		} else {
			err = p.d.UpdateUser(usr)
		}
		if err != nil {
			response.WriteErrorString(http.StatusInternalServerError, ERROR_INTERNAL)
			log.WithFields(log.Fields{"Err": err}).Warn("Error while updating User")
			return
		}
		response.WriteEntity(true)
		return
	} else {
		response.WriteErrorString(http.StatusNotFound, "User not found. Please register first")
		return
	}
}

func (p *UserWebService) CreateUser(request *restful.Request, response *restful.Response) {
	usr := new(db.User)
	err := request.ReadEntity(usr)
	log.WithFields(log.Fields{"Username": usr.Name}).Info("Attempted user registration")
	log.Debug(usr)
	if err != nil {
		response.WriteErrorString(http.StatusBadRequest, ERROR_INVALID_INPUT)
		log.WithFields(log.Fields{"Error Msg": err}).Info(ERROR_INVALID_INPUT)
		return
	}

	ex := p.d.CheckUserExistance(usr)

	if ex {
		response.WriteErrorString(http.StatusUnauthorized, "This username is not available")
		return
	}
	err = usr.Secret.SetPassword(usr.Password)
	if err != nil {
	} else {

		err = p.d.CreateUser(usr)
	}
	if err != nil {
		response.WriteErrorString(http.StatusInternalServerError, ERROR_INTERNAL)
		log.WithFields(log.Fields{"Error Msg": err}).Warn(ERROR_INTERNAL)
		return
	}
	response.WriteEntity("/user/" + usr.Name)
}

func (p *UserWebService) DeleteUser(request *restful.Request, response *restful.Response) {
	name := request.PathParameter("name")
	log.WithFields(log.Fields{"Name": name}).Info("Got user DELETE request")
	err := p.d.DeleteUser(name)
	if err != nil {
		log.WithFields(log.Fields{"Error Msg": err}).Info(ERROR_INTERNAL)
		response.WriteErrorString(http.StatusInternalServerError, ERROR_INTERNAL)
		return
	}
	response.WriteEntity(true)
}
<commit_msg>Only allow deletion of the users own account<commit_after>/*
 *    Copyright (C) 2015 Stefan Luecke
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Affero General Public License as published
 *    by the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *    Authors: Stefan Luecke <glaxx@glaxx.net>
 */

package webservice

import (
	log "github.com/Sirupsen/logrus"
	"github.com/emicklei/go-restful"
	//"html/template"
	"net/http"
	//"strconv"
	//"strings"
	db "github.com/openlab-aux/lsmsd/database"
)

type UserWebService struct {
	d *db.UserDBProvider
	S *restful.WebService
	a *BasicAuthService
}

func NewUserService(d *db.UserDBProvider, a *BasicAuthService) *UserWebService {
	res := new(UserWebService)
	res.d = d
	res.a = a
	service := new(restful.WebService)
	service.
		Path("/user").
		Doc("User related services").
		ApiVersion("0.1").
		Consumes(restful.MIME_JSON).
		Produces(restful.MIME_JSON)

	service.Route(service.GET("/{name}").
		Param(restful.PathParameter("name", "User identifier")).
		Doc("Returns public accessable information about the referenced user").
		To(res.GetUserByName).
		Writes(db.User{}).
		Do(returnsInternalServerError, returnsNotFound, returnsBadRequest))

	service.Route(service.GET("/{name}/log").
		Param(restful.PathParameter("name", "User identifier")).
		Doc("Returns all changes the user has made in the DB").
		To(res.GetUserLogByName).
		Writes(db.UserActionHistory{}).
		Do(returnsInternalServerError, returnsNotFound, returnsBadRequest))

	service.Route(service.GET("").
		Doc("List all users (this may be replaced by a paginated version)").
		To(res.ListUser).
		Writes([]db.User{}).
		Do(returnsInternalServerError))

	service.Route(service.PUT("").
		Filter(res.a.Auth).
		Doc("Update user information").
		To(res.UpdateUser).
		Reads(db.User{}).
		Do(returnsInternalServerError, returnsNotFound, returnsUpdateSuccessful, returnsBadRequest))

	service.Route(service.POST("").
		Doc("Register a new user").
		To(res.CreateUser).
		Reads(db.User{}).
		Returns(http.StatusOK, "Insert successful", "/user/{name}").
		Returns(http.StatusUnauthorized, "This username is not available", nil).
		Do(returnsInternalServerError, returnsBadRequest))

	service.Route(service.DELETE("/{name}").
		Filter(res.a.Auth).
		Param(restful.PathParameter("name", "User identifier")).
		Doc("Delete a user").
		To(res.DeleteUser).
		Returns(http.StatusForbidden, "Request not allowed; You may only delete your own account", nil).
		Do(returnsInternalServerError, returnsNotFound, returnsDeleteSuccessful, returnsBadRequest))
	res.S = service
	return res
}

func (p *UserWebService) GetUserByName(request *restful.Request, response *restful.Response) {
	name := request.PathParameter("name")
	user, err := p.d.GetUserByName(name)
	if err != nil {
		response.WriteErrorString(http.StatusBadRequest, ERROR_INVALID_ID)
		log.WithFields(log.Fields{"Error Msg": err}).
			Info(ERROR_INVALID_ID)
		return
	}
	response.WriteEntity(user)
}

func (p *UserWebService) GetUserLogByName(request *restful.Request, response *restful.Response) {
	name := request.PathParameter("name")
	ul, err := p.d.GetUserLogByName(name)
	if err != nil {
		response.WriteErrorString(http.StatusNotFound, ERROR_INVALID_ID)
		log.Info(err)
		return
	}
	response.WriteEntity(ul)
	return
}

func (p *UserWebService) ListUser(request *restful.Request, response *restful.Response) {
	usr, err := p.d.ListUser()
	if err != nil {
		response.WriteErrorString(http.StatusInternalServerError, ERROR_INTERNAL)
		log.WithFields(log.Fields{"Error Msg": err}).Warn(ERROR_INTERNAL)
		return
	}
	response.WriteEntity(usr)
}

func (p *UserWebService) UpdateUser(request *restful.Request, response *restful.Response) {
	usr := new(db.User)
	err := request.ReadEntity(usr)
	if err != nil {
		response.WriteErrorString(http.StatusBadRequest, ERROR_INVALID_INPUT)
		log.WithFields(log.Fields{"Error Msg": err}).Warn(ERROR_INVALID_INPUT)
		return
	}

	ex := p.d.CheckUserExistance(usr)
	if ex {
		if usr.Password != "" {
			log.Debug("User supplied new password.")
			err = usr.Secret.SetPassword(usr.Password)
		} else {
			temp, err := p.d.GetUserByName(usr.Name)
			if err != nil {
			} else {
				usr.Secret = temp.Secret // if no new password will be set, preserve old
			}
		}
		if err != nil { //fall through to error handling
		} else {
			err = p.d.UpdateUser(usr)
		}
		if err != nil {
			response.WriteErrorString(http.StatusInternalServerError, ERROR_INTERNAL)
			log.WithFields(log.Fields{"Err": err}).Warn("Error while updating User")
			return
		}
		response.WriteEntity(true)
		return
	} else {
		response.WriteErrorString(http.StatusNotFound, "User not found. Please register first")
		return
	}
}

func (p *UserWebService) CreateUser(request *restful.Request, response *restful.Response) {
	usr := new(db.User)
	err := request.ReadEntity(usr)
	log.WithFields(log.Fields{"Username": usr.Name}).Info("Attempted user registration")
	log.Debug(usr)
	if err != nil {
		response.WriteErrorString(http.StatusBadRequest, ERROR_INVALID_INPUT)
		log.WithFields(log.Fields{"Error Msg": err}).Info(ERROR_INVALID_INPUT)
		return
	}

	ex := p.d.CheckUserExistance(usr)

	if ex {
		response.WriteErrorString(http.StatusUnauthorized, "This username is not available")
		return
	}
	err = usr.Secret.SetPassword(usr.Password)
	if err != nil {
	} else {

		err = p.d.CreateUser(usr)
	}
	if err != nil {
		response.WriteErrorString(http.StatusInternalServerError, ERROR_INTERNAL)
		log.WithFields(log.Fields{"Error Msg": err}).Warn(ERROR_INTERNAL)
		return
	}
	response.WriteEntity("/user/" + usr.Name)
}

func (p *UserWebService) DeleteUser(request *restful.Request, response *restful.Response) {
	name := request.PathParameter("name")
	log.WithFields(log.Fields{"Name": name}).Info("Got user DELETE request")

	if name != request.Attribute("User").(string) {
		log.WithFields(log.Fields{"User": request.Attribute("User").(string), "attempted to delete": name}).Warn("Unauthorized deletion request")
		response.WriteErrorString(http.StatusForbidden, "Request not allowed")
		return
	}

	err := p.d.DeleteUser(name)
	if err != nil {
		log.WithFields(log.Fields{"Error Msg": err}).Info(ERROR_INTERNAL)
		response.WriteErrorString(http.StatusInternalServerError, ERROR_INTERNAL)
		return
	}
	response.WriteEntity(true)
}
<|endoftext|>


<commit_before>package jiffybox

import (
	"bytes"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

const (
	STATUS_START    = "START"
	STATUS_SHUTDOWN = "SHUTDOWN"
	STATUS_PULLPLUG = "PULLPLUG"
	STATUS_FREEZE   = "FREEZE"
	STATUS_THAW     = "THAW"
)

type Disk struct {
	Name       string `json:"name"`       //" Swap",
	Filesystem string `json:"filesystem"` // "swap",
	SizeInMB   int    `json:"sizeInMB"`   // 512,
	Created    int    `json:"created"`    // 1234567890,
	Status     string `json:"status"`     // "READY"
}

type ActiveProfile struct {
	Name         string           `json:"name"`         // "Standard",
	Created      int              `json:"created"`      // 1234567890,
	Runlevel     string           `json:"runlevel"`     // "default",
	Kernel       string           `json:"kernel"`       // "xen-current",
	Rootdisk     string           `json:"rootdisk"`     // "\/dev\/xvda",
	RootdiskMode string           `json:"rootdiskMode"` // "ro",
	Status       string           `json:"status"`       // "READY",
	DisksHash    map[string]*Disk `json:"disks"`
}

func (ap *ActiveProfile) Disks() (disks []*Disk) {
	for _, disk := range ap.DisksHash {
		disks = append(disks, disk)
	}
	return disks

}

type Server struct {
	Id                  int                 `json:"id"`
	Name                string              `json:"name"`
	Ips                 map[string][]string `json:"ips"`
	Status              string              `json:"status"`
	Created             int64               `json:"created"`
	RecoverymodeActive  bool                `json:"recoverymodeActive"`
	ManualBackupRunning bool                `json:"manualBackupRunning"`
	IsBeingCopied       bool                `json:"isBeingCopied"`
	Running             bool                `json:"running"`
	Host                string              `json:"host"`
	Plan                *Plan               `json:"plan"`
	ActiveProfile       *ActiveProfile      `json:"activeProfile"`
	Metadata            map[string]string   `json:"metadata"`
}

func (server *Server) IpsString() string {
	ips := []string{}
	for k, v := range server.Ips {
		ips = append(ips, k+":"+strings.Join(v, ","))
	}
	return strings.Join(ips, ", ")
}

func (server *Server) CreatedAt() time.Time {
	return time.Unix(server.Created, 0)
}

func (server *Server) PublicIp() string {
	if ips := server.Ips["public"]; len(ips) > 0 {
		return ips[0]
	}
	return ""
}

type Message struct {
	Type    string `json:"type"`
	Message string `json:"message"`
}

type JiffyBoxResponse struct {
	Messages []*Message
	Server   *Server `json:"result"`
}

type JiffyBoxesResponse struct {
	Messages   []*Message
	ServersMap map[string]*Server `json:"result"`
}

func (rsp *JiffyBoxesResponse) Servers() []*Server {
	servers := make([]*Server, 0, len(rsp.ServersMap))
	for _, server := range rsp.ServersMap {
		servers = append(servers, server)
	}
	return servers
}

func (rsp *JiffyBoxesResponse) Server() *Server {
	for _, server := range rsp.ServersMap {
		return server
	}
	return nil
}

func (client *Client) JiffyBoxes() (servers []*Server, e error) {
	rsp := &JiffyBoxesResponse{}
	e = client.LoadResource("jiffyBoxes", rsp)
	if e != nil {
		return servers, e
	}
	return rsp.Servers(), nil
}

func (client *Client) JiffyBox(id int) (server *Server, e error) {
	rsp := &JiffyBoxResponse{}
	e = client.LoadResource("jiffyBoxes/"+strconv.Itoa(id), rsp)
	if e != nil {
		return server, e
	}
	return rsp.Server, nil
}

func (client *Client) DeleteJiffyBox(id string) (e error) {
	req, e := http.NewRequest("DELETE", client.BaseUrl()+"/jiffyBoxes/"+id, nil)
	if e != nil {
		return e
	}
	httpResponse, e := http.DefaultClient.Do(req)
	if e != nil {
		return e
	}
	rsp := &Response{}
	e = client.unmarshalResponse(httpResponse, rsp)
	if e != nil {
		return e
	}
	return nil
}

type CreateOptions struct {
	Name         string
	PlanId       int
	BackupId     int
	Distribution string
	Password     string
	UseSshKey    bool
	Metadata     string
}

func (client *Client) CreateJiffyBox(options *CreateOptions) (server *Server, e error) {
	values := url.Values{}
	values.Add("name", options.Name)
	values.Add("planid", strconv.Itoa(options.PlanId))
	if options.BackupId > 0 {
		values.Add("backupid", strconv.Itoa(options.BackupId))
	}
	if options.Distribution != "" {
		values.Add("distribution", options.Distribution)
	}
	if options.Password != "" {
		values.Add("password", options.Password)
	}
	if options.UseSshKey {
		values.Add("use_sshkey", "true")
	}
	if options.Metadata != "" {
		values.Add("metadata", options.Metadata)
	}
	httpResponse, e := http.PostForm(client.BaseUrl()+"/jiffyBoxes", values)
	if e != nil {
		return nil, e
	}
	rsp := &JiffyBoxResponse{}
	e = client.unmarshalResponse(httpResponse, rsp)
	return rsp.Server, e
}

// unfreeze server
func (client *Client) ThawServer(id int) (server *Server, e error) {
	return client.ChangeState(id, STATUS_THAW, -1)
}

func (client *Client) FreezeServer(id int) (server *Server, e error) {
	return client.ChangeState(id, STATUS_FREEZE, -1)
}

func (client *Client) PullPlugServer(id int) (server *Server, e error) {
	return client.ChangeState(id, STATUS_PULLPLUG, -1)
}

func (client *Client) ShutdownServer(id int) (server *Server, e error) {
	return client.ChangeState(id, STATUS_SHUTDOWN, -1)
}

func (client *Client) StartServer(id int, planId int) (server *Server, e error) {
	return client.ChangeState(id, STATUS_START, planId)
}

func (client *Client) ChangeState(id int, state string, planId int) (server *Server, e error) {
	values := url.Values{}
	values.Add("status", state)
	if planId > 0 {
		values.Add("planid", strconv.Itoa(planId))
	}
	buf := bytes.Buffer{}
	buf.WriteString(values.Encode())
	req, e := http.NewRequest("PUT", client.BaseUrl()+"/jiffyBoxes/"+strconv.Itoa(id), &buf)
	if e != nil {
		return nil, e
	}
	httpResponse, e := http.DefaultClient.Do(req)
	if e != nil {
		return nil, e
	}
	rsp := &JiffyBoxResponse{}
	e = client.unmarshalResponse(httpResponse, rsp)
	return rsp.Server, e
}

func (client *Client) CloneServer(id int, opts *CreateOptions) (server *Server, e error) {
	values := url.Values{}
	if opts.Name == "" {
		return nil, fmt.Errorf("name must be provided to clone server")
	}
	values.Add("name", opts.Name)
	values.Add("planid", strconv.Itoa(opts.PlanId))
	if opts.Metadata != "" {
		values.Add("metadata", opts.Metadata)
	}
	httpResponse, e := client.PostForm("jiffyBoxes/"+strconv.Itoa(id), values)
	if e != nil {
		return nil, e
	}
	rsp := &JiffyBoxResponse{}
	e = client.unmarshal(httpResponse.Content, rsp)
	if e != nil {
		return nil, e
	}
	return rsp.Server, nil
}

func (client *Client) ChangeStatus(id string, status string, planId int, metadata string) (server *Server, e error) {
	values := url.Values{}
	values.Add("status", status)
	values.Add("planid", strconv.Itoa(planId))
	if metadata != "" {
		values.Add("metadata", metadata)
	}
	buf := &bytes.Buffer{}
	buf.Write([]byte(values.Encode()))
	req, e := http.NewRequest("PUT", client.BaseUrl()+"/JiffyBox/"+id, buf)
	if e != nil {
		return nil, e
	}
	httpResponse, e := http.DefaultClient.Do(req)
	if e != nil {
		return nil, e
	}
	rsp := &JiffyBoxResponse{}
	e = client.unmarshalResponse(httpResponse, rsp)
	if e != nil {
		return nil, e
	}
	return rsp.Server, nil
}
<commit_msg>catch errors on changing states<commit_after>package jiffybox

import (
	"bytes"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

const (
	STATUS_START    = "START"
	STATUS_SHUTDOWN = "SHUTDOWN"
	STATUS_PULLPLUG = "PULLPLUG"
	STATUS_FREEZE   = "FREEZE"
	STATUS_THAW     = "THAW"
)

type Disk struct {
	Name       string `json:"name"`       //" Swap",
	Filesystem string `json:"filesystem"` // "swap",
	SizeInMB   int    `json:"sizeInMB"`   // 512,
	Created    int    `json:"created"`    // 1234567890,
	Status     string `json:"status"`     // "READY"
}

type ActiveProfile struct {
	Name         string           `json:"name"`         // "Standard",
	Created      int              `json:"created"`      // 1234567890,
	Runlevel     string           `json:"runlevel"`     // "default",
	Kernel       string           `json:"kernel"`       // "xen-current",
	Rootdisk     string           `json:"rootdisk"`     // "\/dev\/xvda",
	RootdiskMode string           `json:"rootdiskMode"` // "ro",
	Status       string           `json:"status"`       // "READY",
	DisksHash    map[string]*Disk `json:"disks"`
}

func (ap *ActiveProfile) Disks() (disks []*Disk) {
	for _, disk := range ap.DisksHash {
		disks = append(disks, disk)
	}
	return disks

}

type Server struct {
	Id                  int                 `json:"id"`
	Name                string              `json:"name"`
	Ips                 map[string][]string `json:"ips"`
	Status              string              `json:"status"`
	Created             int64               `json:"created"`
	RecoverymodeActive  bool                `json:"recoverymodeActive"`
	ManualBackupRunning bool                `json:"manualBackupRunning"`
	IsBeingCopied       bool                `json:"isBeingCopied"`
	Running             bool                `json:"running"`
	Host                string              `json:"host"`
	Plan                *Plan               `json:"plan"`
	ActiveProfile       *ActiveProfile      `json:"activeProfile"`
	Metadata            map[string]string   `json:"metadata"`
}

func (server *Server) IpsString() string {
	ips := []string{}
	for k, v := range server.Ips {
		ips = append(ips, k+":"+strings.Join(v, ","))
	}
	return strings.Join(ips, ", ")
}

func (server *Server) CreatedAt() time.Time {
	return time.Unix(server.Created, 0)
}

func (server *Server) PublicIp() string {
	if ips := server.Ips["public"]; len(ips) > 0 {
		return ips[0]
	}
	return ""
}

type Message struct {
	Type    string `json:"type"`
	Message string `json:"message"`
}

type JiffyBoxResponse struct {
	Messages []*Message
	Server   *Server `json:"result"`
}

type JiffyBoxesResponse struct {
	Messages   []*Message
	ServersMap map[string]*Server `json:"result"`
}

func (rsp *JiffyBoxesResponse) Servers() []*Server {
	servers := make([]*Server, 0, len(rsp.ServersMap))
	for _, server := range rsp.ServersMap {
		servers = append(servers, server)
	}
	return servers
}

func (rsp *JiffyBoxesResponse) Server() *Server {
	for _, server := range rsp.ServersMap {
		return server
	}
	return nil
}

func (client *Client) JiffyBoxes() (servers []*Server, e error) {
	rsp := &JiffyBoxesResponse{}
	e = client.LoadResource("jiffyBoxes", rsp)
	if e != nil {
		return servers, e
	}
	return rsp.Servers(), nil
}

func (client *Client) JiffyBox(id int) (server *Server, e error) {
	rsp := &JiffyBoxResponse{}
	e = client.LoadResource("jiffyBoxes/"+strconv.Itoa(id), rsp)
	if e != nil {
		return server, e
	}
	return rsp.Server, nil
}

func (client *Client) DeleteJiffyBox(id string) (e error) {
	req, e := http.NewRequest("DELETE", client.BaseUrl()+"/jiffyBoxes/"+id, nil)
	if e != nil {
		return e
	}
	httpResponse, e := http.DefaultClient.Do(req)
	if e != nil {
		return e
	}
	rsp := &Response{}
	e = client.unmarshalResponse(httpResponse, rsp)
	if e != nil {
		return e
	}
	return nil
}

type CreateOptions struct {
	Name         string
	PlanId       int
	BackupId     int
	Distribution string
	Password     string
	UseSshKey    bool
	Metadata     string
}

func (client *Client) CreateJiffyBox(options *CreateOptions) (server *Server, e error) {
	values := url.Values{}
	values.Add("name", options.Name)
	values.Add("planid", strconv.Itoa(options.PlanId))
	if options.BackupId > 0 {
		values.Add("backupid", strconv.Itoa(options.BackupId))
	}
	if options.Distribution != "" {
		values.Add("distribution", options.Distribution)
	}
	if options.Password != "" {
		values.Add("password", options.Password)
	}
	if options.UseSshKey {
		values.Add("use_sshkey", "true")
	}
	if options.Metadata != "" {
		values.Add("metadata", options.Metadata)
	}
	httpResponse, e := http.PostForm(client.BaseUrl()+"/jiffyBoxes", values)
	if e != nil {
		return nil, e
	}
	rsp := &JiffyBoxResponse{}
	e = client.unmarshalResponse(httpResponse, rsp)
	return rsp.Server, e
}

// unfreeze server
func (client *Client) ThawServer(id int) (server *Server, e error) {
	return client.ChangeState(id, STATUS_THAW, -1)
}

func (client *Client) FreezeServer(id int) (server *Server, e error) {
	return client.ChangeState(id, STATUS_FREEZE, -1)
}

func (client *Client) PullPlugServer(id int) (server *Server, e error) {
	return client.ChangeState(id, STATUS_PULLPLUG, -1)
}

func (client *Client) ShutdownServer(id int) (server *Server, e error) {
	return client.ChangeState(id, STATUS_SHUTDOWN, -1)
}

func (client *Client) StartServer(id int, planId int) (server *Server, e error) {
	return client.ChangeState(id, STATUS_START, planId)
}

func (client *Client) ChangeState(id int, state string, planId int) (server *Server, e error) {
	values := url.Values{}
	values.Add("status", state)
	if planId > 0 {
		values.Add("planid", strconv.Itoa(planId))
	}
	buf := bytes.Buffer{}
	buf.WriteString(values.Encode())
	req, e := http.NewRequest("PUT", client.BaseUrl()+"/jiffyBoxes/"+strconv.Itoa(id), &buf)
	if e != nil {
		return nil, e
	}
	httpResponse, e := http.DefaultClient.Do(req)
	if e != nil {
		return nil, e
	}
	rsp := &JiffyBoxResponse{}
	e = client.unmarshalResponse(httpResponse, rsp)
	if e != nil {
		return rsp.Server, e
	}
	errors := []string{}
	for _, message := range rsp.Messages {
		if message.Type == "error" {
			errors = append(errors, message.Message)
		}
	}
	if len(errors) > 0 {
		return rsp.Server, fmt.Errorf(strings.Join(errors, ", "))
	}
	return rsp.Server, e
}

func (client *Client) CloneServer(id int, opts *CreateOptions) (server *Server, e error) {
	values := url.Values{}
	if opts.Name == "" {
		return nil, fmt.Errorf("name must be provided to clone server")
	}
	values.Add("name", opts.Name)
	values.Add("planid", strconv.Itoa(opts.PlanId))
	if opts.Metadata != "" {
		values.Add("metadata", opts.Metadata)
	}
	httpResponse, e := client.PostForm("jiffyBoxes/"+strconv.Itoa(id), values)
	if e != nil {
		return nil, e
	}
	rsp := &JiffyBoxResponse{}
	e = client.unmarshal(httpResponse.Content, rsp)
	if e != nil {
		return nil, e
	}
	return rsp.Server, nil
}

func (client *Client) ChangeStatus(id string, status string, planId int, metadata string) (server *Server, e error) {
	values := url.Values{}
	values.Add("status", status)
	values.Add("planid", strconv.Itoa(planId))
	if metadata != "" {
		values.Add("metadata", metadata)
	}
	buf := &bytes.Buffer{}
	buf.Write([]byte(values.Encode()))
	req, e := http.NewRequest("PUT", client.BaseUrl()+"/JiffyBox/"+id, buf)
	if e != nil {
		return nil, e
	}
	httpResponse, e := http.DefaultClient.Do(req)
	if e != nil {
		return nil, e
	}
	rsp := &JiffyBoxResponse{}
	e = client.unmarshalResponse(httpResponse, rsp)
	if e != nil {
		return nil, e
	}
	return rsp.Server, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package pvcprotection

import (
	"fmt"
	"time"

	v1 "k8s.io/api/core/v1"
	apierrs "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/apimachinery/pkg/util/wait"
	coreinformers "k8s.io/client-go/informers/core/v1"
	clientset "k8s.io/client-go/kubernetes"
	corelisters "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"
	"k8s.io/klog"
	"k8s.io/kubernetes/pkg/controller"
	"k8s.io/kubernetes/pkg/controller/volume/protectionutil"
	"k8s.io/kubernetes/pkg/util/metrics"
	"k8s.io/kubernetes/pkg/util/slice"
	volumeutil "k8s.io/kubernetes/pkg/volume/util"
)

// Controller is controller that removes PVCProtectionFinalizer
// from PVCs that are used by no pods.
type Controller struct {
	client clientset.Interface

	pvcLister       corelisters.PersistentVolumeClaimLister
	pvcListerSynced cache.InformerSynced

	podLister       corelisters.PodLister
	podListerSynced cache.InformerSynced

	queue workqueue.RateLimitingInterface

	// allows overriding of StorageObjectInUseProtection feature Enabled/Disabled for testing
	storageObjectInUseProtectionEnabled bool
}

// NewPVCProtectionController returns a new instance of PVCProtectionController.
func NewPVCProtectionController(pvcInformer coreinformers.PersistentVolumeClaimInformer, podInformer coreinformers.PodInformer, cl clientset.Interface, storageObjectInUseProtectionFeatureEnabled bool) *Controller {
	e := &Controller{
		client:                              cl,
		queue:                               workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "pvcprotection"),
		storageObjectInUseProtectionEnabled: storageObjectInUseProtectionFeatureEnabled,
	}
	if cl != nil && cl.CoreV1().RESTClient().GetRateLimiter() != nil {
		metrics.RegisterMetricAndTrackRateLimiterUsage("persistentvolumeclaim_protection_controller", cl.CoreV1().RESTClient().GetRateLimiter())
	}

	e.pvcLister = pvcInformer.Lister()
	e.pvcListerSynced = pvcInformer.Informer().HasSynced
	pvcInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: e.pvcAddedUpdated,
		UpdateFunc: func(old, new interface{}) {
			e.pvcAddedUpdated(new)
		},
	})

	e.podLister = podInformer.Lister()
	e.podListerSynced = podInformer.Informer().HasSynced
	podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			e.podAddedDeletedUpdated(nil, obj, false)
		},
		DeleteFunc: func(obj interface{}) {
			e.podAddedDeletedUpdated(nil, obj, true)
		},
		UpdateFunc: func(old, new interface{}) {
			e.podAddedDeletedUpdated(old, new, false)
		},
	})

	return e
}

// Run runs the controller goroutines.
func (c *Controller) Run(workers int, stopCh <-chan struct{}) {
	defer utilruntime.HandleCrash()
	defer c.queue.ShutDown()

	klog.Infof("Starting PVC protection controller")
	defer klog.Infof("Shutting down PVC protection controller")

	if !controller.WaitForCacheSync("PVC protection", stopCh, c.pvcListerSynced, c.podListerSynced) {
		return
	}

	for i := 0; i < workers; i++ {
		go wait.Until(c.runWorker, time.Second, stopCh)
	}

	<-stopCh
}

func (c *Controller) runWorker() {
	for c.processNextWorkItem() {
	}
}

// processNextWorkItem deals with one pvcKey off the queue.  It returns false when it's time to quit.
func (c *Controller) processNextWorkItem() bool {
	pvcKey, quit := c.queue.Get()
	if quit {
		return false
	}
	defer c.queue.Done(pvcKey)

	pvcNamespace, pvcName, err := cache.SplitMetaNamespaceKey(pvcKey.(string))
	if err != nil {
		utilruntime.HandleError(fmt.Errorf("Error parsing PVC key %q: %v", pvcKey, err))
		return true
	}

	err = c.processPVC(pvcNamespace, pvcName)
	if err == nil {
		c.queue.Forget(pvcKey)
		return true
	}

	utilruntime.HandleError(fmt.Errorf("PVC %v failed with : %v", pvcKey, err))
	c.queue.AddRateLimited(pvcKey)

	return true
}

func (c *Controller) processPVC(pvcNamespace, pvcName string) error {
	klog.V(4).Infof("Processing PVC %s/%s", pvcNamespace, pvcName)
	startTime := time.Now()
	defer func() {
		klog.V(4).Infof("Finished processing PVC %s/%s (%v)", pvcNamespace, pvcName, time.Since(startTime))
	}()

	pvc, err := c.pvcLister.PersistentVolumeClaims(pvcNamespace).Get(pvcName)
	if apierrs.IsNotFound(err) {
		klog.V(4).Infof("PVC %s/%s not found, ignoring", pvcNamespace, pvcName)
		return nil
	}
	if err != nil {
		return err
	}

	if protectionutil.IsDeletionCandidate(pvc, volumeutil.PVCProtectionFinalizer) {
		// PVC should be deleted. Check if it's used and remove finalizer if
		// it's not.
		isUsed, err := c.isBeingUsed(pvc)
		if err != nil {
			return err
		}
		if !isUsed {
			return c.removeFinalizer(pvc)
		}
		klog.V(2).Infof("Keeping PVC %s/%s because it is still being used", pvc.Namespace, pvc.Name)
	}

	if protectionutil.NeedToAddFinalizer(pvc, volumeutil.PVCProtectionFinalizer) {
		// PVC is not being deleted -> it should have the finalizer. The
		// finalizer should be added by admission plugin, this is just to add
		// the finalizer to old PVCs that were created before the admission
		// plugin was enabled.
		return c.addFinalizer(pvc)
	}
	return nil
}

func (c *Controller) addFinalizer(pvc *v1.PersistentVolumeClaim) error {
	// Skip adding Finalizer in case the StorageObjectInUseProtection feature is not enabled
	if !c.storageObjectInUseProtectionEnabled {
		return nil
	}
	claimClone := pvc.DeepCopy()
	claimClone.ObjectMeta.Finalizers = append(claimClone.ObjectMeta.Finalizers, volumeutil.PVCProtectionFinalizer)
	_, err := c.client.CoreV1().PersistentVolumeClaims(claimClone.Namespace).Update(claimClone)
	if err != nil {
		klog.V(3).Infof("Error adding protection finalizer to PVC %s/%s: %v", pvc.Namespace, pvc.Name, err)
		return err
	}
	klog.V(3).Infof("Added protection finalizer to PVC %s/%s", pvc.Namespace, pvc.Name)
	return nil
}

func (c *Controller) removeFinalizer(pvc *v1.PersistentVolumeClaim) error {
	claimClone := pvc.DeepCopy()
	claimClone.ObjectMeta.Finalizers = slice.RemoveString(claimClone.ObjectMeta.Finalizers, volumeutil.PVCProtectionFinalizer, nil)
	_, err := c.client.CoreV1().PersistentVolumeClaims(claimClone.Namespace).Update(claimClone)
	if err != nil {
		klog.V(3).Infof("Error removing protection finalizer from PVC %s/%s: %v", pvc.Namespace, pvc.Name, err)
		return err
	}
	klog.V(3).Infof("Removed protection finalizer from PVC %s/%s", pvc.Namespace, pvc.Name)
	return nil
}

func (c *Controller) isBeingUsed(pvc *v1.PersistentVolumeClaim) (bool, error) {
	// Look for a Pod using pvc in the Informer's cache. If one is found the
	// correct decision to keep pvc is taken without doing an expensive live
	// list.
	if inUse, err := c.askInformer(pvc); err != nil {
		// No need to return because a live list will follow.
		klog.Error(err)
	} else if inUse {
		return true, nil
	}

	// Even if no Pod using pvc was found in the Informer's cache it doesn't
	// mean such a Pod doesn't exist: it might just not be in the cache yet. To
	// be 100% confident that it is safe to delete pvc make sure no Pod is using
	// it among those returned by a live list.
	return c.askAPIServer(pvc)
}

func (c *Controller) askInformer(pvc *v1.PersistentVolumeClaim) (bool, error) {
	klog.V(4).Infof("Looking for Pods using PVC %s/%s in the Informer's cache", pvc.Namespace, pvc.Name)

	pods, err := c.podLister.Pods(pvc.Namespace).List(labels.Everything())
	if err != nil {
		return false, fmt.Errorf("Cache-based list of pods failed while processing %s/%s: %s", pvc.Namespace, pvc.Name, err.Error())
	}

	for _, pod := range pods {
		if podUsesPVC(pod, pvc.Name) {
			return true, nil
		}
	}

	klog.V(4).Infof("No Pod using PVC %s/%s was found in the Informer's cache", pvc.Namespace, pvc.Name)
	return false, nil
}

func (c *Controller) askAPIServer(pvc *v1.PersistentVolumeClaim) (bool, error) {
	klog.V(4).Infof("Looking for Pods using PVC %s/%s with a live list", pvc.Namespace, pvc.Name)

	podsList, err := c.client.CoreV1().Pods(pvc.Namespace).List(metav1.ListOptions{})
	if err != nil {
		return false, fmt.Errorf("Live list of pods failed: %s", err.Error())
	}

	for _, pod := range podsList.Items {
		if podUsesPVC(&pod, pvc.Name) {
			return true, nil
		}
	}

	klog.V(2).Infof("PVC %s/%s is unused", pvc.Namespace, pvc.Name)
	return false, nil
}

func podUsesPVC(pod *v1.Pod, pvc string) bool {
	// Check whether pvc is used by pod only if pod is scheduled, because
	// kubelet sees pods after they have been scheduled and it won't allow
	// starting a pod referencing a PVC with a non-nil deletionTimestamp.
	if pod.Spec.NodeName != "" {
		for _, volume := range pod.Spec.Volumes {
			if volume.PersistentVolumeClaim != nil && volume.PersistentVolumeClaim.ClaimName == pvc {
				klog.V(2).Infof("Pod %s/%s uses PVC %s", pod.Namespace, pod.Name, pvc)
				return true
			}
		}
	}
	return false
}

// pvcAddedUpdated reacts to pvc added/updated events
func (c *Controller) pvcAddedUpdated(obj interface{}) {
	pvc, ok := obj.(*v1.PersistentVolumeClaim)
	if !ok {
		utilruntime.HandleError(fmt.Errorf("PVC informer returned non-PVC object: %#v", obj))
		return
	}
	key, err := cache.MetaNamespaceKeyFunc(pvc)
	if err != nil {
		utilruntime.HandleError(fmt.Errorf("Couldn't get key for Persistent Volume Claim %#v: %v", pvc, err))
		return
	}
	klog.V(4).Infof("Got event on PVC %s", key)

	if protectionutil.NeedToAddFinalizer(pvc, volumeutil.PVCProtectionFinalizer) || protectionutil.IsDeletionCandidate(pvc, volumeutil.PVCProtectionFinalizer) {
		c.queue.Add(key)
	}
}

// podAddedDeletedUpdated reacts to Pod events
func (c *Controller) podAddedDeletedUpdated(old, new interface{}, deleted bool) {
	if pod := c.parsePod(new); pod != nil {
		c.enqueuePVCs(pod, deleted)

		// An update notification might mask the deletion of a pod X and the
		// following creation of a pod Y with the same namespaced name as X. If
		// that's the case X needs to be processed as well to handle the case
		// where it is blocking deletion of a PVC not referenced by Y, otherwise
		// such PVC will never be deleted.
		if oldPod := c.parsePod(old); oldPod != nil && oldPod.UID != pod.UID {
			c.enqueuePVCs(oldPod, true)
		}
	}
}

func (*Controller) parsePod(obj interface{}) *v1.Pod {
	if obj == nil {
		return nil
	}
	pod, ok := obj.(*v1.Pod)
	if !ok {
		tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
		if !ok {
			utilruntime.HandleError(fmt.Errorf("Couldn't get object from tombstone %#v", obj))
			return nil
		}
		pod, ok = tombstone.Obj.(*v1.Pod)
		if !ok {
			utilruntime.HandleError(fmt.Errorf("Tombstone contained object that is not a Pod %#v", obj))
			return nil
		}
	}
	return pod
}

func (c *Controller) enqueuePVCs(pod *v1.Pod, deleted bool) {
	// Filter out pods that can't help us to remove a finalizer on PVC
	if !deleted && !volumeutil.IsPodTerminated(pod, pod.Status) && pod.Spec.NodeName != "" {
		return
	}

	klog.V(4).Infof("Enqueuing PVCs for Pod %s/%s (UID=%s)", pod.Namespace, pod.Name, pod.UID)

	// Enqueue all PVCs that the pod uses
	for _, volume := range pod.Spec.Volumes {
		if volume.PersistentVolumeClaim != nil {
			c.queue.Add(pod.Namespace + "/" + volume.PersistentVolumeClaim.ClaimName)
		}
	}
}
<commit_msg>Lowercase first letter of chainable errors in PVC protection controller<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package pvcprotection

import (
	"fmt"
	"time"

	v1 "k8s.io/api/core/v1"
	apierrs "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/apimachinery/pkg/util/wait"
	coreinformers "k8s.io/client-go/informers/core/v1"
	clientset "k8s.io/client-go/kubernetes"
	corelisters "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"
	"k8s.io/klog"
	"k8s.io/kubernetes/pkg/controller"
	"k8s.io/kubernetes/pkg/controller/volume/protectionutil"
	"k8s.io/kubernetes/pkg/util/metrics"
	"k8s.io/kubernetes/pkg/util/slice"
	volumeutil "k8s.io/kubernetes/pkg/volume/util"
)

// Controller is controller that removes PVCProtectionFinalizer
// from PVCs that are used by no pods.
type Controller struct {
	client clientset.Interface

	pvcLister       corelisters.PersistentVolumeClaimLister
	pvcListerSynced cache.InformerSynced

	podLister       corelisters.PodLister
	podListerSynced cache.InformerSynced

	queue workqueue.RateLimitingInterface

	// allows overriding of StorageObjectInUseProtection feature Enabled/Disabled for testing
	storageObjectInUseProtectionEnabled bool
}

// NewPVCProtectionController returns a new instance of PVCProtectionController.
func NewPVCProtectionController(pvcInformer coreinformers.PersistentVolumeClaimInformer, podInformer coreinformers.PodInformer, cl clientset.Interface, storageObjectInUseProtectionFeatureEnabled bool) *Controller {
	e := &Controller{
		client:                              cl,
		queue:                               workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "pvcprotection"),
		storageObjectInUseProtectionEnabled: storageObjectInUseProtectionFeatureEnabled,
	}
	if cl != nil && cl.CoreV1().RESTClient().GetRateLimiter() != nil {
		metrics.RegisterMetricAndTrackRateLimiterUsage("persistentvolumeclaim_protection_controller", cl.CoreV1().RESTClient().GetRateLimiter())
	}

	e.pvcLister = pvcInformer.Lister()
	e.pvcListerSynced = pvcInformer.Informer().HasSynced
	pvcInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: e.pvcAddedUpdated,
		UpdateFunc: func(old, new interface{}) {
			e.pvcAddedUpdated(new)
		},
	})

	e.podLister = podInformer.Lister()
	e.podListerSynced = podInformer.Informer().HasSynced
	podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			e.podAddedDeletedUpdated(nil, obj, false)
		},
		DeleteFunc: func(obj interface{}) {
			e.podAddedDeletedUpdated(nil, obj, true)
		},
		UpdateFunc: func(old, new interface{}) {
			e.podAddedDeletedUpdated(old, new, false)
		},
	})

	return e
}

// Run runs the controller goroutines.
func (c *Controller) Run(workers int, stopCh <-chan struct{}) {
	defer utilruntime.HandleCrash()
	defer c.queue.ShutDown()

	klog.Infof("Starting PVC protection controller")
	defer klog.Infof("Shutting down PVC protection controller")

	if !controller.WaitForCacheSync("PVC protection", stopCh, c.pvcListerSynced, c.podListerSynced) {
		return
	}

	for i := 0; i < workers; i++ {
		go wait.Until(c.runWorker, time.Second, stopCh)
	}

	<-stopCh
}

func (c *Controller) runWorker() {
	for c.processNextWorkItem() {
	}
}

// processNextWorkItem deals with one pvcKey off the queue.  It returns false when it's time to quit.
func (c *Controller) processNextWorkItem() bool {
	pvcKey, quit := c.queue.Get()
	if quit {
		return false
	}
	defer c.queue.Done(pvcKey)

	pvcNamespace, pvcName, err := cache.SplitMetaNamespaceKey(pvcKey.(string))
	if err != nil {
		utilruntime.HandleError(fmt.Errorf("error parsing PVC key %q: %v", pvcKey, err))
		return true
	}

	err = c.processPVC(pvcNamespace, pvcName)
	if err == nil {
		c.queue.Forget(pvcKey)
		return true
	}

	utilruntime.HandleError(fmt.Errorf("PVC %v failed with : %v", pvcKey, err))
	c.queue.AddRateLimited(pvcKey)

	return true
}

func (c *Controller) processPVC(pvcNamespace, pvcName string) error {
	klog.V(4).Infof("Processing PVC %s/%s", pvcNamespace, pvcName)
	startTime := time.Now()
	defer func() {
		klog.V(4).Infof("Finished processing PVC %s/%s (%v)", pvcNamespace, pvcName, time.Since(startTime))
	}()

	pvc, err := c.pvcLister.PersistentVolumeClaims(pvcNamespace).Get(pvcName)
	if apierrs.IsNotFound(err) {
		klog.V(4).Infof("PVC %s/%s not found, ignoring", pvcNamespace, pvcName)
		return nil
	}
	if err != nil {
		return err
	}

	if protectionutil.IsDeletionCandidate(pvc, volumeutil.PVCProtectionFinalizer) {
		// PVC should be deleted. Check if it's used and remove finalizer if
		// it's not.
		isUsed, err := c.isBeingUsed(pvc)
		if err != nil {
			return err
		}
		if !isUsed {
			return c.removeFinalizer(pvc)
		}
		klog.V(2).Infof("Keeping PVC %s/%s because it is still being used", pvc.Namespace, pvc.Name)
	}

	if protectionutil.NeedToAddFinalizer(pvc, volumeutil.PVCProtectionFinalizer) {
		// PVC is not being deleted -> it should have the finalizer. The
		// finalizer should be added by admission plugin, this is just to add
		// the finalizer to old PVCs that were created before the admission
		// plugin was enabled.
		return c.addFinalizer(pvc)
	}
	return nil
}

func (c *Controller) addFinalizer(pvc *v1.PersistentVolumeClaim) error {
	// Skip adding Finalizer in case the StorageObjectInUseProtection feature is not enabled
	if !c.storageObjectInUseProtectionEnabled {
		return nil
	}
	claimClone := pvc.DeepCopy()
	claimClone.ObjectMeta.Finalizers = append(claimClone.ObjectMeta.Finalizers, volumeutil.PVCProtectionFinalizer)
	_, err := c.client.CoreV1().PersistentVolumeClaims(claimClone.Namespace).Update(claimClone)
	if err != nil {
		klog.V(3).Infof("Error adding protection finalizer to PVC %s/%s: %v", pvc.Namespace, pvc.Name, err)
		return err
	}
	klog.V(3).Infof("Added protection finalizer to PVC %s/%s", pvc.Namespace, pvc.Name)
	return nil
}

func (c *Controller) removeFinalizer(pvc *v1.PersistentVolumeClaim) error {
	claimClone := pvc.DeepCopy()
	claimClone.ObjectMeta.Finalizers = slice.RemoveString(claimClone.ObjectMeta.Finalizers, volumeutil.PVCProtectionFinalizer, nil)
	_, err := c.client.CoreV1().PersistentVolumeClaims(claimClone.Namespace).Update(claimClone)
	if err != nil {
		klog.V(3).Infof("Error removing protection finalizer from PVC %s/%s: %v", pvc.Namespace, pvc.Name, err)
		return err
	}
	klog.V(3).Infof("Removed protection finalizer from PVC %s/%s", pvc.Namespace, pvc.Name)
	return nil
}

func (c *Controller) isBeingUsed(pvc *v1.PersistentVolumeClaim) (bool, error) {
	// Look for a Pod using pvc in the Informer's cache. If one is found the
	// correct decision to keep pvc is taken without doing an expensive live
	// list.
	if inUse, err := c.askInformer(pvc); err != nil {
		// No need to return because a live list will follow.
		klog.Error(err)
	} else if inUse {
		return true, nil
	}

	// Even if no Pod using pvc was found in the Informer's cache it doesn't
	// mean such a Pod doesn't exist: it might just not be in the cache yet. To
	// be 100% confident that it is safe to delete pvc make sure no Pod is using
	// it among those returned by a live list.
	return c.askAPIServer(pvc)
}

func (c *Controller) askInformer(pvc *v1.PersistentVolumeClaim) (bool, error) {
	klog.V(4).Infof("Looking for Pods using PVC %s/%s in the Informer's cache", pvc.Namespace, pvc.Name)

	pods, err := c.podLister.Pods(pvc.Namespace).List(labels.Everything())
	if err != nil {
		return false, fmt.Errorf("cache-based list of pods failed while processing %s/%s: %s", pvc.Namespace, pvc.Name, err.Error())
	}

	for _, pod := range pods {
		if podUsesPVC(pod, pvc.Name) {
			return true, nil
		}
	}

	klog.V(4).Infof("No Pod using PVC %s/%s was found in the Informer's cache", pvc.Namespace, pvc.Name)
	return false, nil
}

func (c *Controller) askAPIServer(pvc *v1.PersistentVolumeClaim) (bool, error) {
	klog.V(4).Infof("Looking for Pods using PVC %s/%s with a live list", pvc.Namespace, pvc.Name)

	podsList, err := c.client.CoreV1().Pods(pvc.Namespace).List(metav1.ListOptions{})
	if err != nil {
		return false, fmt.Errorf("live list of pods failed: %s", err.Error())
	}

	for _, pod := range podsList.Items {
		if podUsesPVC(&pod, pvc.Name) {
			return true, nil
		}
	}

	klog.V(2).Infof("PVC %s/%s is unused", pvc.Namespace, pvc.Name)
	return false, nil
}

func podUsesPVC(pod *v1.Pod, pvc string) bool {
	// Check whether pvc is used by pod only if pod is scheduled, because
	// kubelet sees pods after they have been scheduled and it won't allow
	// starting a pod referencing a PVC with a non-nil deletionTimestamp.
	if pod.Spec.NodeName != "" {
		for _, volume := range pod.Spec.Volumes {
			if volume.PersistentVolumeClaim != nil && volume.PersistentVolumeClaim.ClaimName == pvc {
				klog.V(2).Infof("Pod %s/%s uses PVC %s", pod.Namespace, pod.Name, pvc)
				return true
			}
		}
	}
	return false
}

// pvcAddedUpdated reacts to pvc added/updated events
func (c *Controller) pvcAddedUpdated(obj interface{}) {
	pvc, ok := obj.(*v1.PersistentVolumeClaim)
	if !ok {
		utilruntime.HandleError(fmt.Errorf("PVC informer returned non-PVC object: %#v", obj))
		return
	}
	key, err := cache.MetaNamespaceKeyFunc(pvc)
	if err != nil {
		utilruntime.HandleError(fmt.Errorf("couldn't get key for Persistent Volume Claim %#v: %v", pvc, err))
		return
	}
	klog.V(4).Infof("Got event on PVC %s", key)

	if protectionutil.NeedToAddFinalizer(pvc, volumeutil.PVCProtectionFinalizer) || protectionutil.IsDeletionCandidate(pvc, volumeutil.PVCProtectionFinalizer) {
		c.queue.Add(key)
	}
}

// podAddedDeletedUpdated reacts to Pod events
func (c *Controller) podAddedDeletedUpdated(old, new interface{}, deleted bool) {
	if pod := c.parsePod(new); pod != nil {
		c.enqueuePVCs(pod, deleted)

		// An update notification might mask the deletion of a pod X and the
		// following creation of a pod Y with the same namespaced name as X. If
		// that's the case X needs to be processed as well to handle the case
		// where it is blocking deletion of a PVC not referenced by Y, otherwise
		// such PVC will never be deleted.
		if oldPod := c.parsePod(old); oldPod != nil && oldPod.UID != pod.UID {
			c.enqueuePVCs(oldPod, true)
		}
	}
}

func (*Controller) parsePod(obj interface{}) *v1.Pod {
	if obj == nil {
		return nil
	}
	pod, ok := obj.(*v1.Pod)
	if !ok {
		tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
		if !ok {
			utilruntime.HandleError(fmt.Errorf("couldn't get object from tombstone %#v", obj))
			return nil
		}
		pod, ok = tombstone.Obj.(*v1.Pod)
		if !ok {
			utilruntime.HandleError(fmt.Errorf("tombstone contained object that is not a Pod %#v", obj))
			return nil
		}
	}
	return pod
}

func (c *Controller) enqueuePVCs(pod *v1.Pod, deleted bool) {
	// Filter out pods that can't help us to remove a finalizer on PVC
	if !deleted && !volumeutil.IsPodTerminated(pod, pod.Status) && pod.Spec.NodeName != "" {
		return
	}

	klog.V(4).Infof("Enqueuing PVCs for Pod %s/%s (UID=%s)", pod.Namespace, pod.Name, pod.UID)

	// Enqueue all PVCs that the pod uses
	for _, volume := range pod.Spec.Volumes {
		if volume.PersistentVolumeClaim != nil {
			c.queue.Add(pod.Namespace + "/" + volume.PersistentVolumeClaim.ClaimName)
		}
	}
}
<|endoftext|>


<commit_before>package journal

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"os"
	"strconv"
	"strings"
	"syscall"
)

type Priority int

const (
	PriEmerg Priority = iota
	PriAlert
	PriCrit
	PriErr
	PriWarning
	PriNotice
	PriInfo
	PriDebug
)

var conn net.Conn

func init() {
	var err error
	conn, err = net.Dial("unixgram", "/run/systemd/journal/socket")
	if err != nil {
		fmt.Fprintln(os.Stderr, "socket connection error:", err)
	}
}

func Enabled() bool {
	return conn != nil
}

func Send(message string, priority Priority, vars map[string]string) {
	if conn == nil {
		fmt.Fprintln(os.Stderr, "journal error: connection uninitialized")
		return
	}

	data := new(bytes.Buffer)
	appendVariable(data, "PRIORITY", strconv.Itoa(int(priority)))
	appendVariable(data, "MESSAGE", message)
	for k, v := range vars {
		appendVariable(data, k, v)
	}

	_, err := io.Copy(conn, data)
	if err != nil && isSocketSpaceError(err) {
		file, err := tempFd()
		if err != nil {
			fmt.Fprintln(os.Stderr, "journal error:", err)
			return
		}
		_, err = io.Copy(file, data)
		if err != nil {
			fmt.Fprintln(os.Stderr, "journal error:", err)
			return
		}

		rights := syscall.UnixRights(int(file.Fd()))

		/* this connection should always be a UnixConn, but better safe than sorry */
		unixConn, ok := conn.(*net.UnixConn)
		if !ok {
			fmt.Fprintln(os.Stderr, "journal error: can't send file through non-Unix connection")
			return
		}

		unixConn.WriteMsgUnix([]byte{}, rights, nil)
	} else if err != nil {
		fmt.Fprintln(os.Stderr, "journal error:", err)
	}
}

func appendVariable(w io.Writer, name, value string) {
	if !validVarName(name) {
		fmt.Fprintln(os.Stderr, "journal error: variable name contains invalid character, ignoring")
		return
	}
	if strings.ContainsRune(value, '\n') {
		/* When the value contains a newline, we write:
		 * - the variable name, followed by a newline
		 * - the size (in 64bit little endian format)
		 * - the data, followed by a newline
		 */
		fmt.Fprintln(w, name)
		w.Write(toBytes(uint64(len(value))))
		fmt.Fprintln(w, value)
	} else {
		/* just write the variable and value all on one line */
		fmt.Fprintf(w, "%s=%s\n", name, value)
	}
}

func validVarName(name string) bool {
	/* The variable name must be in uppercase and consist only of characters,
	 * numbers and underscores, and may not begin with an underscore. (from the docs)
	 */
	valid := true
	valid = valid && name[0] != '_'
	for _, c := range name {
		valid = valid && ('A' <= c && c <= 'Z') || ('0' <= c && c <='9') || c == '_'
	}
	return valid
}

func isSocketSpaceError(err error) bool {
	opErr, ok := err.(*net.OpError)
	if !ok {
		return false
	}

	sysErr, ok := opErr.Err.(syscall.Errno)
	if !ok {
		return false
	}

	return sysErr == syscall.EMSGSIZE || sysErr == syscall.ENOBUFS
}

func toBytes(n uint64) []byte {
	b := make([]byte, 8)
	for i := 0; i < 8; i++ {
		b[i] = byte(n >> uint(i*8))
	}
	return b
}

func tempFd() (*os.File, error) {
	file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX")
	if err != nil {
		return nil, err
	}
	syscall.Unlink(file.Name())
	if err != nil {
		return nil, err
	}
	return file, nil
}
<commit_msg>Add better error reporting<commit_after>package journal

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"os"
	"strconv"
	"strings"
	"syscall"
)

type Priority int

const (
	PriEmerg Priority = iota
	PriAlert
	PriCrit
	PriErr
	PriWarning
	PriNotice
	PriInfo
	PriDebug
)

var conn net.Conn

func init() {
	var err error
	conn, err = net.Dial("unixgram", "/run/systemd/journal/socket")
	if err != nil {
		journalError(err.Error())
	}
}

func Enabled() bool {
	return conn != nil
}

func Send(message string, priority Priority, vars map[string]string) error {
	if conn == nil {
		return journalError("could not connect to journald socket")
	}

	data := new(bytes.Buffer)
	appendVariable(data, "PRIORITY", strconv.Itoa(int(priority)))
	appendVariable(data, "MESSAGE", message)
	for k, v := range vars {
		appendVariable(data, k, v)
	}

	_, err := io.Copy(conn, data)
	if err != nil && isSocketSpaceError(err) {
		file, err := tempFd()
		if err != nil {
			return journalError(err.Error())
		}
		_, err = io.Copy(file, data)
		if err != nil {
			return journalError(err.Error())
		}

		rights := syscall.UnixRights(int(file.Fd()))

		/* this connection should always be a UnixConn, but better safe than sorry */
		unixConn, ok := conn.(*net.UnixConn)
		if !ok {
			return journalError("can't send file through non-Unix connection")
		}
		unixConn.WriteMsgUnix([]byte{}, rights, nil)
	} else if err != nil {
		return journalError(err.Error())
	}
	return nil
}

func appendVariable(w io.Writer, name, value string) {
	if !validVarName(name) {
		journalError("variable name contains invalid character, ignoring")
	}
	if strings.ContainsRune(value, '\n') {
		/* When the value contains a newline, we write:
		 * - the variable name, followed by a newline
		 * - the size (in 64bit little endian format)
		 * - the data, followed by a newline
		 */
		fmt.Fprintln(w, name)
		w.Write(toBytes(uint64(len(value))))
		fmt.Fprintln(w, value)
	} else {
		/* just write the variable and value all on one line */
		fmt.Fprintf(w, "%s=%s\n", name, value)
	}
}

func validVarName(name string) bool {
	/* The variable name must be in uppercase and consist only of characters,
	 * numbers and underscores, and may not begin with an underscore. (from the docs)
	 */
	valid := true
	valid = valid && name[0] != '_'
	for _, c := range name {
		valid = valid && ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_'
	}
	return valid
}

func isSocketSpaceError(err error) bool {
	opErr, ok := err.(*net.OpError)
	if !ok {
		return false
	}

	sysErr, ok := opErr.Err.(syscall.Errno)
	if !ok {
		return false
	}

	return sysErr == syscall.EMSGSIZE || sysErr == syscall.ENOBUFS
}

func toBytes(n uint64) []byte {
	b := make([]byte, 8)
	for i := 0; i < 8; i++ {
		b[i] = byte(n >> uint(i*8))
	}
	return b
}

func tempFd() (*os.File, error) {
	file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX")
	if err != nil {
		return nil, err
	}
	syscall.Unlink(file.Name())
	if err != nil {
		return nil, err
	}
	return file, nil
}

func journalError(s string) error {
	s = "journal error: " + s
	fmt.Fprintln(os.Stderr, s)
	return errors.New(s)
}
<|endoftext|>


<commit_before>package entity

import "time"

type URL struct {
	ID         int64 `gorm:"primary_key"`
	OriginUrl  string
	ShortPath  string
	CreateTime time.Time
}
<commit_msg>OriginUrl -> OriginURL<commit_after>package entity

import "time"

type URL struct {
	ID         int64 `gorm:"primary_key"`
	OriginURL  string
	ShortPath  string
	CreateTime time.Time
}
<|endoftext|>