Merge pull request #4083 from tb365/feature/tb-user-batch-import

Feature/tb user batch import
This commit is contained in:
yunion-ci-robot
2019-12-19 19:04:04 +08:00
committed by GitHub
2 changed files with 212 additions and 42 deletions
+204 -11
View File
@@ -24,10 +24,12 @@ import (
"time"
"github.com/360EntSecGroup-Skylar/excelize"
"golang.org/x/sync/errgroup"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/apigateway/options"
"yunion.io/x/onecloud/pkg/appctx"
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/httperrors"
@@ -36,6 +38,19 @@ import (
"yunion.io/x/onecloud/pkg/mcclient/modules"
)
const (
HOST_MAC = "*MAC地址"
HOST_NAME = "*名称"
HOST_IPMI_ADDR = "*IPMI地址"
HOST_IPMI_USERNAME = "*IPMI用户名"
HOST_IPMI_PASSWORD = "*IPMI密码"
HOST_MNG_IP_ADDR = "*管理口IP地址"
HOST_IPMI_ADDR_OPTIONAL = "IPMI地址"
HOST_IPMI_USERNAME_OPTIONAL = "IPMI用户名"
HOST_IPMI_PASSWORD_OPTIONAL = "IPMI密码"
HOST_MNG_IP_ADDR_OPTIONAL = "管理口IP地址"
)
func FetchSession(ctx context.Context, r *http.Request, apiVersion string) *mcclient.ClientSession {
token := AppContextToken(ctx)
session := auth.GetSession(ctx, token, FetchRegion(r), apiVersion)
@@ -96,6 +111,10 @@ func (mh *MiscHandler) PostUploads(ctx context.Context, w http.ResponseWriter, r
case "BatchHostRegister":
mh.DoBatchHostRegister(ctx, w, req)
return
// 用户批量注册
case "BatchUserRegister":
mh.DoBatchUserRegister(ctx, w, req)
return
default:
err := httperrors.NewInputParameterError("Unsupported action %s", actions[0])
httperrors.JsonClientError(w, err)
@@ -135,22 +154,48 @@ func (mh *MiscHandler) DoBatchHostRegister(ctx context.Context, w http.ResponseW
log.Errorf(err.Error())
e := httperrors.NewInternalServerError("can't parse file")
httperrors.JsonClientError(w, e)
return
}
rows := xlsx.GetRows("hosts")
if len(rows) == 0 {
e := httperrors.NewGeneralError(fmt.Errorf("empty file content"))
httperrors.JsonClientError(w, e)
return
}
h := ""
paramKeys := []string{}
for _, title := range rows[0] {
switch title {
case HOST_MAC:
paramKeys = append(paramKeys, "access_mac")
case HOST_NAME:
paramKeys = append(paramKeys, "name")
case HOST_IPMI_ADDR, HOST_IPMI_ADDR_OPTIONAL:
paramKeys = append(paramKeys, "ipmi_ip_addr")
case HOST_IPMI_USERNAME, HOST_IPMI_USERNAME_OPTIONAL:
paramKeys = append(paramKeys, "ipmi_username")
case HOST_IPMI_PASSWORD, HOST_IPMI_PASSWORD_OPTIONAL:
paramKeys = append(paramKeys, "ipmi_password")
case HOST_MNG_IP_ADDR, HOST_MNG_IP_ADDR_OPTIONAL:
paramKeys = append(paramKeys, "access_ip")
default:
e := httperrors.NewInternalServerError("empty file content")
httperrors.JsonClientError(w, e)
return
}
}
// skipped header row
for _, row := range xlsx.GetRows("hosts")[1:] {
if len(row) < 4 {
log.Debugf("batchHostRegister row length too short (less than 4) %s", row)
}
h = h + strings.Join(row, ",") + "\n"
}
s := FetchSession(ctx, req, "")
params := jsonutils.NewDict()
params.Set("hosts", jsonutils.NewString(h))
resp, err := modules.Hosts.DoBatchRegister(s, params)
resp, err := modules.Hosts.DoBatchRegister(s, paramKeys, params)
if err != nil {
e := httperrors.NewGeneralError(err)
httperrors.JsonClientError(w, e)
@@ -160,6 +205,117 @@ func (mh *MiscHandler) DoBatchHostRegister(ctx context.Context, w http.ResponseW
appsrv.SendJSON(w, resp)
}
func (mh *MiscHandler) DoBatchUserRegister(ctx context.Context, w http.ResponseWriter, req *http.Request) {
s := FetchSession(ctx, req, "")
files := req.MultipartForm.File
userfiles, ok := files["users"]
if !ok || len(userfiles) == 0 || userfiles[0] == nil {
e := httperrors.NewInputParameterError("Missing parameter %s", "users")
httperrors.JsonClientError(w, e)
return
}
fileHeader := userfiles[0].Header
contentType := fileHeader.Get("Content-Type")
if contentType != "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" {
e := httperrors.NewInputParameterError("Wrong content type %s, required application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", contentType)
httperrors.JsonClientError(w, e)
return
}
file, err := userfiles[0].Open()
defer file.Close()
if err != nil {
log.Errorf(err.Error())
e := httperrors.NewInternalServerError("can't open file")
httperrors.JsonClientError(w, e)
return
}
xlsx, err := excelize.OpenReader(file)
if err != nil {
log.Errorf(err.Error())
e := httperrors.NewInternalServerError("can't parse file")
httperrors.JsonClientError(w, e)
return
}
// skipped header row
rows := xlsx.GetRows("users")
if len(rows) <= 1 {
e := httperrors.NewInputParameterError("empty file")
httperrors.JsonClientError(w, e)
return
}
users := []jsonutils.JSONObject{}
names := map[string]bool{}
domains := map[string]string{}
for i, row := range rows[1:] {
name := row[0]
if len(name) == 0 {
e := httperrors.NewClientError("row %d name is empty", i+2)
httperrors.JsonClientError(w, e)
return
}
if _, ok := names[name]; ok {
e := httperrors.NewClientError("duplicate name %s", row[0])
httperrors.JsonClientError(w, e)
return
} else {
names[name] = true
_, err := modules.UsersV3.Get(s, name, nil)
if err == nil {
continue
}
}
domainId, ok := domains[row[1]]
if !ok {
id, err := modules.Domains.GetId(s, row[1], nil)
if err != nil {
httperrors.JsonClientError(w, httperrors.NewGeneralError(err))
return
}
domainId = id
domains[row[1]] = id
}
user := jsonutils.NewDict()
user.Add(jsonutils.NewString(name), "name")
user.Add(jsonutils.NewString(domainId), "domain_id")
user.Add(jsonutils.NewString("OneCloud@2019"), "password")
if strings.ToLower(row[2]) == "true" || strings.ToLower(row[2]) == "1" {
user.Add(jsonutils.JSONTrue, "allow_web_console")
} else {
user.Add(jsonutils.JSONFalse, "allow_web_console")
}
users = append(users, user)
}
// batch create
var userG errgroup.Group
for i := range users {
user := users[i]
userG.Go(func() error {
_, err := modules.UsersV3.Create(s, user)
return err
})
}
if err := userG.Wait(); err != nil {
e := httperrors.NewGeneralError(err)
httperrors.GeneralServerError(w, e)
return
}
appsrv.SendJSON(w, jsonutils.NewDict())
}
func (mh *MiscHandler) getDownloadsHandler(ctx context.Context, w http.ResponseWriter, req *http.Request) {
params := appctx.AppContextParams(ctx)
template, ok := params["<template_id>"]
@@ -168,23 +324,60 @@ func (mh *MiscHandler) getDownloadsHandler(ctx context.Context, w http.ResponseW
return
}
var err error
var content bytes.Buffer
switch template {
case "BatchHostRegister":
records := [][]string{{"MAC地址", "名称", "IPMI地址", "IPMI用户名", "IPMI密码"}}
content, err := writeXlsx("hosts", records)
records := [][]string{{HOST_MAC, HOST_NAME, HOST_IPMI_ADDR_OPTIONAL, HOST_IPMI_USERNAME_OPTIONAL, HOST_IPMI_PASSWORD_OPTIONAL}}
content, err = writeXlsx("hosts", records)
if err != nil {
httperrors.InternalServerError(w, "internal server error")
return
}
case "BatchHostISORegister":
records := [][]string{{HOST_NAME, HOST_IPMI_ADDR, HOST_IPMI_USERNAME, HOST_IPMI_PASSWORD, HOST_MNG_IP_ADDR}}
content, err = writeXlsx("hosts", records)
if err != nil {
httperrors.InternalServerError(w, "internal server error")
return
}
case "BatchHostPXERegister":
records := [][]string{{HOST_NAME, HOST_IPMI_ADDR, HOST_IPMI_USERNAME, HOST_IPMI_PASSWORD, HOST_MNG_IP_ADDR_OPTIONAL}}
content, err = writeXlsx("hosts", records)
if err != nil {
httperrors.InternalServerError(w, "internal server error")
return
}
case "BatchUserRegister":
records := [][]string{{"用户名(user)", "部门/域(domain)", "是否登录控制台(allow_web_console:true、false)"}}
content, err = writeXlsx("users", records)
if err != nil {
httperrors.InternalServerError(w, "internal server error")
return
}
case "BatchProjectRegister":
var titles []string
if options.Options.NonDefaultDomainProjects {
titles = []string{"项目名称", "域", "配额"}
} else {
titles = []string{"项目名称", "域"}
}
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
w.Header().Set("Content-Disposition", "Attachment; filename=hosts_template.xlsx")
w.Write(content.Bytes())
return
records := [][]string{titles}
content, err = writeXlsx("projects", records)
if err != nil {
httperrors.InternalServerError(w, "internal server error")
return
}
default:
httperrors.InputParameterError(w, "template not found %s", template)
return
}
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
w.Header().Set("Content-Disposition", "Attachment; filename=template.xlsx")
w.Write(content.Bytes())
return
}
func (mh *MiscHandler) postPIUploads(ctx context.Context, w http.ResponseWriter, req *http.Request) {
+8 -31
View File
@@ -16,7 +16,6 @@ package modules
import (
"fmt"
"regexp"
"strings"
"yunion.io/x/jsonutils"
@@ -89,7 +88,7 @@ func (this *HostManager) GetIpmiInfo(s *mcclient.ClientSession, id string, param
return ret, nil
}
func parseHosts(data string) ([]jsonutils.JSONObject, string) {
func parseHosts(titles []string, data string) ([]jsonutils.JSONObject, string) {
msg := ""
hosts := strings.Split(data, "\n")
ret := []jsonutils.JSONObject{}
@@ -101,37 +100,15 @@ func parseHosts(data string) ([]jsonutils.JSONObject, string) {
}
fields := strings.Split(host, ",")
if len(fields) != 5 {
msg += fmt.Sprintf("第%d行: %s (格式不正确)\n", i, host)
continue
}
// mac address check
if match, err := regexp.MatchString(MACAddressPattern, fields[0]); err != nil || !match {
msg += fmt.Sprintf("第%d行: %s (Mac地址格式不正确)\n", i, host)
continue
}
// name check
if len(fields[1]) == 0 {
msg += fmt.Sprintf("第%d行: %s (名称不能为空)\n", i, host)
}
params := jsonutils.NewDict()
params.Add(jsonutils.NewString(fields[0]), "access_mac")
params.Add(jsonutils.NewString(fields[1]), "name")
params.Add(jsonutils.NewString("baremetal"), "host_type")
if len(fields[2]) > 0 {
params.Add(jsonutils.NewString(fields[2]), "ipmi_ip_addr")
}
if len(fields[3]) > 0 {
params.Add(jsonutils.NewString(fields[3]), "ipmi_username")
}
if len(fields[4]) > 0 {
params.Add(jsonutils.NewString(fields[4]), "ipmi_password")
for i := range fields {
field := fields[i]
if len(field) > 0 {
params.Add(jsonutils.NewString(field), titles[i])
}
}
ret = append(ret, params)
@@ -140,13 +117,13 @@ func parseHosts(data string) ([]jsonutils.JSONObject, string) {
return ret, msg
}
func (this *HostManager) DoBatchRegister(s *mcclient.ClientSession, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
func (this *HostManager) DoBatchRegister(s *mcclient.ClientSession, titles []string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
data, err := params.GetString("hosts")
if err != nil {
return nil, err
}
hosts, msg := parseHosts(data)
hosts, msg := parseHosts(titles, data)
if len(msg) > 0 {
return nil, httperrors.NewInputParameterError(msg)
}