2021-02-25 08:40:04 +01:00
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
2021-05-07 15:43:43 +02:00
|
|
|
"encoding/json"
|
2021-02-25 08:40:04 +01:00
|
|
|
"fmt"
|
2021-05-07 15:43:43 +02:00
|
|
|
"io"
|
2021-02-25 08:40:04 +01:00
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
2021-05-07 15:43:43 +02:00
|
|
|
"strconv"
|
2021-02-25 08:40:04 +01:00
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/OpenSlides/OpenSlides/performance/client"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/vbauerster/mpb"
|
|
|
|
)
|
|
|
|
|
|
|
|
const voteHelp = `Sends many votes from different users.
|
|
|
|
|
|
|
|
This command requires, that there are many user created at the
|
|
|
|
backend. You can use the command "create_users" for this job.
|
|
|
|
|
2021-05-07 15:43:43 +02:00
|
|
|
Example:
|
2021-02-25 08:40:04 +01:00
|
|
|
|
2021-05-07 15:43:43 +02:00
|
|
|
performance votes motion --amount 100 --poll_id 42
|
|
|
|
|
|
|
|
performance votes assignment --amount 100 --poll_id 42
|
|
|
|
|
|
|
|
`
|
2021-02-25 08:40:04 +01:00
|
|
|
|
2021-05-07 15:43:43 +02:00
|
|
|
func cmdVotes(cfg *config) *cobra.Command {
|
2021-02-25 08:40:04 +01:00
|
|
|
cmd := &cobra.Command{
|
2021-05-07 15:43:43 +02:00
|
|
|
Use: "votes",
|
|
|
|
Short: "Sends many votes from different users",
|
|
|
|
ValidArgs: []string{"motion", "assignment", "m", "a"},
|
|
|
|
Args: cobra.ExactValidArgs(1),
|
|
|
|
Long: voteHelp,
|
|
|
|
}
|
|
|
|
|
|
|
|
amount := cmd.Flags().IntP("amount", "n", 10, "Amount of users to use.")
|
|
|
|
pollID := cmd.Flags().IntP("poll_id", "i", 1, "ID of the poll to use.")
|
|
|
|
interrupt := cmd.Flags().Bool("interrupt", false, "Wait for a user input after login.")
|
|
|
|
loginRetry := cmd.Flags().IntP("login_retry", "r", 3, "Retries send login requests before returning an error.")
|
|
|
|
choice := cmd.Flags().IntP("choice", "c", 0, "Amount of answers per vote.")
|
|
|
|
|
|
|
|
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
|
|
|
var assignment bool
|
|
|
|
if args[0] == "assignment" || args[0] == "a" {
|
|
|
|
assignment = true
|
|
|
|
}
|
|
|
|
|
|
|
|
url := "%s/rest/motions/motion-poll/%d/vote/"
|
|
|
|
vote := `{"data":"Y"}`
|
|
|
|
if assignment {
|
|
|
|
url = "%s/rest/assignments/assignment-poll/%d/vote/"
|
|
|
|
options, err := assignmentPollOptions(cfg.addr(), *pollID)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("fetch options: %w", err)
|
2021-02-25 08:40:04 +01:00
|
|
|
}
|
|
|
|
|
2021-05-07 15:43:43 +02:00
|
|
|
votedata := make(map[string]int)
|
|
|
|
for i, o := range options {
|
|
|
|
if *choice != 0 && i >= *choice {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
votedata[strconv.Itoa(o)] = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
decoded, err := json.Marshal(votedata)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("decoding assignment option data: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
vote = fmt.Sprintf(`{"data":%s}`, decoded)
|
|
|
|
}
|
|
|
|
url = fmt.Sprintf(url, cfg.addr(), *pollID)
|
|
|
|
|
|
|
|
var clients []*client.Client
|
|
|
|
for i := 0; i < *amount; i++ {
|
|
|
|
c, err := client.New(cfg.addr(), fmt.Sprintf("dummy%d", i+1), "pass", client.WithLoginRetry(*loginRetry))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("creating client: %w", err)
|
|
|
|
}
|
|
|
|
clients = append(clients, c)
|
|
|
|
}
|
2021-02-25 08:40:04 +01:00
|
|
|
|
2021-05-07 15:43:43 +02:00
|
|
|
log.Printf("Login %d clients", *amount)
|
|
|
|
start := time.Now()
|
|
|
|
massLogin(clients, *loginRetry)
|
|
|
|
log.Printf("All clients logged in %v", time.Now().Sub(start))
|
|
|
|
|
|
|
|
if *interrupt {
|
|
|
|
reader := bufio.NewReader(os.Stdin)
|
|
|
|
fmt.Println("Hit enter to continue")
|
|
|
|
reader.ReadString('\n')
|
|
|
|
log.Println("Starting voting")
|
|
|
|
}
|
|
|
|
|
|
|
|
start = time.Now()
|
|
|
|
massVotes(clients, url, vote)
|
|
|
|
log.Printf("All Clients have voted in %v", time.Now().Sub(start))
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
2021-02-25 08:40:04 +01:00
|
|
|
|
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
2021-05-07 15:43:43 +02:00
|
|
|
func assignmentPollOptions(addr string, pollID int) ([]int, error) {
|
|
|
|
c, err := client.New(addr, "dummy1", "pass")
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("creating admin client: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := c.Login(); err != nil {
|
|
|
|
return nil, fmt.Errorf("login admin client: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", fmt.Sprintf("%s/system/autoupdate", addr), nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("creating fetching all request: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := c.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("sending fetch all request: %w", err)
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
2021-02-25 08:40:04 +01:00
|
|
|
if err != nil {
|
2021-05-07 15:43:43 +02:00
|
|
|
body = []byte(fmt.Sprintf("[can not read body: %v", err))
|
2021-02-25 08:40:04 +01:00
|
|
|
}
|
2021-05-07 15:43:43 +02:00
|
|
|
return nil, fmt.Errorf("got status %s: %s", resp.Status, body)
|
2021-02-25 08:40:04 +01:00
|
|
|
}
|
|
|
|
|
2021-05-07 15:43:43 +02:00
|
|
|
// First message is a connection info. Throw it away.
|
|
|
|
var devNull json.RawMessage
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&devNull); err != nil {
|
|
|
|
return nil, fmt.Errorf("decoding response: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var content struct {
|
|
|
|
Changed map[string][]json.RawMessage `json:"changed"`
|
|
|
|
}
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&content); err != nil {
|
|
|
|
return nil, fmt.Errorf("decoding response: %w", err)
|
|
|
|
}
|
2021-02-25 08:40:04 +01:00
|
|
|
|
2021-05-07 15:43:43 +02:00
|
|
|
polls := content.Changed["assignments/assignment-poll"]
|
|
|
|
if len(polls) == 0 {
|
|
|
|
return nil, fmt.Errorf("no polls found")
|
2021-02-25 08:40:04 +01:00
|
|
|
}
|
|
|
|
|
2021-05-07 15:43:43 +02:00
|
|
|
for i, p := range polls {
|
|
|
|
var poll struct {
|
|
|
|
ID int `json:"id"`
|
|
|
|
OptionIDs []int `json:"options_id"`
|
|
|
|
}
|
|
|
|
if err := json.Unmarshal(p, &poll); err != nil {
|
|
|
|
return nil, fmt.Errorf("decoding %dth poll: %w", i, err)
|
|
|
|
}
|
|
|
|
if poll.ID != pollID {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
return poll.OptionIDs, nil
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("no assignment-poll with id %d", pollID)
|
2021-02-25 08:40:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func massLogin(clients []*client.Client, tries int) {
|
|
|
|
var wgLogin sync.WaitGroup
|
|
|
|
progress := mpb.New(mpb.WithWaitGroup(&wgLogin))
|
|
|
|
loginBar := progress.AddBar(int64(len(clients)))
|
|
|
|
|
|
|
|
for i := 0; i < len(clients); i++ {
|
|
|
|
wgLogin.Add(1)
|
|
|
|
go func(c *client.Client) {
|
|
|
|
defer wgLogin.Done()
|
|
|
|
|
|
|
|
if err := c.Login(); err != nil {
|
|
|
|
log.Printf("Login failed: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
loginBar.Increment()
|
|
|
|
}(clients[i])
|
|
|
|
}
|
|
|
|
progress.Wait()
|
|
|
|
}
|
|
|
|
|
2021-05-07 15:43:43 +02:00
|
|
|
func massVotes(clients []*client.Client, url string, vote string) {
|
2021-02-25 08:40:04 +01:00
|
|
|
var wgVote sync.WaitGroup
|
|
|
|
progress := mpb.New(mpb.WithWaitGroup(&wgVote))
|
|
|
|
voteBar := progress.AddBar(int64(len(clients)))
|
|
|
|
for i := 0; i < len(clients); i++ {
|
|
|
|
wgVote.Add(1)
|
|
|
|
go func(c *client.Client) {
|
|
|
|
defer wgVote.Done()
|
|
|
|
|
2021-05-07 15:43:43 +02:00
|
|
|
req, err := http.NewRequest("POST", url, strings.NewReader(vote))
|
2021-02-25 08:40:04 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("Error creating request: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
|
|
|
|
if _, err := client.CheckStatus(c.Do(req)); err != nil {
|
2021-05-07 15:43:43 +02:00
|
|
|
log.Printf("Error sending vote request to %s: %v", url, err)
|
2021-02-25 08:40:04 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
voteBar.Increment()
|
|
|
|
return
|
|
|
|
}(clients[i])
|
|
|
|
}
|
|
|
|
progress.Wait()
|
|
|
|
}
|