mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/yunionio/cloudpods.git
synced 2026-09-20 08:03:53 +08:00
glance重构 update 1
Conflicts: Gopkg.lock pkg/appsrv/appsrv.go pkg/cloudcommon/options.go pkg/compute/models/hosts.go pkg/compute/models/quotas.go pkg/compute/service/service.go pkg/mcclient/mcclient.go
This commit is contained in:
@@ -164,6 +164,10 @@
|
||||
name = "k8s.io/client-go"
|
||||
version = "9.0.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/anacrolix/torrent"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "yunion.io/x/jsonutils"
|
||||
|
||||
@@ -11,7 +11,7 @@ func init() {
|
||||
Limit int64 `help:"Limit, default 0, i.e. no limit" default:"20"`
|
||||
Offset int64 `help:"Offset, default 0, i.e. no offset"`
|
||||
Region string `help:"Search by region"`
|
||||
ServiceId string `help:"Search by service id"`
|
||||
Service string `help:"Search by service id or name"`
|
||||
Interface string `help:"Search by interface"`
|
||||
}
|
||||
R(&EndpointListOptions{}, "endpoint-list", "List service endpoints", func(s *mcclient.ClientSession, args *EndpointListOptions) error {
|
||||
@@ -29,8 +29,16 @@ func init() {
|
||||
if len(args.Region) > 0 {
|
||||
query.Add(jsonutils.NewString(args.Region), "region_id")
|
||||
}
|
||||
if len(args.ServiceId) > 0 {
|
||||
query.Add(jsonutils.NewString(args.ServiceId), "service_id")
|
||||
if len(args.Service) > 0 {
|
||||
srvMod, err := modules.GetModule(s, "services")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srvId, err := srvMod.GetId(s, args.Service, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
query.Add(jsonutils.NewString(srvId), "service_id")
|
||||
}
|
||||
if len(args.Interface) > 0 {
|
||||
query.Add(jsonutils.NewString(args.Interface), "interface")
|
||||
|
||||
@@ -396,4 +396,35 @@ func init() {
|
||||
return nil
|
||||
})
|
||||
|
||||
R(&ImageShowOptions{}, "image-private", "Make a image private", func(s *mcclient.ClientSession, args *ImageShowOptions) error {
|
||||
if len(args.ID) == 0 {
|
||||
return fmt.Errorf("No image ID provided")
|
||||
} else if len(args.ID) == 1 {
|
||||
result, err := modules.Images.PerformAction(s, args.ID[0], "private", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(result)
|
||||
} else {
|
||||
results := modules.Images.BatchPerformAction(s, args.ID, "private", nil)
|
||||
printBatchResults(results, modules.Images.GetColumns(s))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
R(&ImageShowOptions{}, "image-public", "Make a image public", func(s *mcclient.ClientSession, args *ImageShowOptions) error {
|
||||
if len(args.ID) == 0 {
|
||||
return fmt.Errorf("No image ID provided")
|
||||
} else if len(args.ID) == 1 {
|
||||
result, err := modules.Images.PerformAction(s, args.ID[0], "public", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(result)
|
||||
} else {
|
||||
results := modules.Images.BatchPerformAction(s, args.ID, "public", nil)
|
||||
printBatchResults(results, modules.Images.GetColumns(s))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,70 +7,28 @@ import (
|
||||
)
|
||||
|
||||
type QuotaBaseOptions struct {
|
||||
Cpu int64 `help:"CPU count"`
|
||||
Memory int64 `help:"Memory size in MB"`
|
||||
Storage int64 `help:"Storage size in MB"`
|
||||
Port int64 `help:"Internal NIC count"`
|
||||
Eport int64 `help:"External NIC count"`
|
||||
Eip int64 `help:"Elastic IP count"`
|
||||
Bw int64 `help:"Internal bandwidth in Mbps"`
|
||||
Ebw int64 `help:"External bandwidth in Mbps"`
|
||||
Image int64 `help:"Template count"`
|
||||
IsolatedDevice int64 `help:"Isolated device count"`
|
||||
Snapshot int64 `help:"Snapshot count"`
|
||||
Cpu int64 `help:"CPU count" json:"cpu:omitzero"`
|
||||
Memory int64 `help:"Memory size in MB" json:"memory,omitzero"`
|
||||
Storage int64 `help:"Storage size in MB" json:"storage,omitzero"`
|
||||
Port int64 `help:"Internal NIC count" json:"port,omitzero"`
|
||||
Eport int64 `help:"External NIC count" json:"eport,omitzero"`
|
||||
Eip int64 `help:"Elastic IP count" json:"eip,omitzero"`
|
||||
Bw int64 `help:"Internal bandwidth in Mbps" json:"bw,omitzero"`
|
||||
Ebw int64 `help:"External bandwidth in Mbps" json:"ebw,omitzero"`
|
||||
IsolatedDevice int64 `help:"Isolated device count" json:"isolated_device,omitzero"`
|
||||
Snapshot int64 `help:"Snapshot count" json:"snapshot,omitzero"`
|
||||
}
|
||||
|
||||
func quotaArgs2Params(args *QuotaBaseOptions) *jsonutils.JSONDict {
|
||||
params := jsonutils.NewDict()
|
||||
if args.Cpu > 0 {
|
||||
params.Add(jsonutils.NewInt(args.Cpu), "cpu")
|
||||
}
|
||||
if args.Memory > 0 {
|
||||
params.Add(jsonutils.NewInt(args.Memory), "memory")
|
||||
}
|
||||
if args.Storage > 0 {
|
||||
params.Add(jsonutils.NewInt(args.Storage), "storage")
|
||||
}
|
||||
if args.Image > 0 {
|
||||
params.Add(jsonutils.NewInt(args.Image), "image")
|
||||
}
|
||||
if args.Port > 0 {
|
||||
params.Add(jsonutils.NewInt(args.Port), "port")
|
||||
}
|
||||
if args.Eport > 0 {
|
||||
params.Add(jsonutils.NewInt(args.Eport), "eport")
|
||||
}
|
||||
if args.Eip > 0 {
|
||||
params.Add(jsonutils.NewInt(args.Eip), "eip")
|
||||
}
|
||||
if args.Bw > 0 {
|
||||
params.Add(jsonutils.NewInt(args.Bw), "bw")
|
||||
}
|
||||
if args.Ebw > 0 {
|
||||
params.Add(jsonutils.NewInt(args.Ebw), "ebw")
|
||||
}
|
||||
if args.IsolatedDevice > 0 {
|
||||
params.Add(jsonutils.NewInt(args.IsolatedDevice), "isolated_device")
|
||||
}
|
||||
if args.Snapshot > 0 {
|
||||
params.Add(jsonutils.NewInt(args.Snapshot), "snapshot")
|
||||
}
|
||||
return params
|
||||
type ImageQuotaBaseOptions struct {
|
||||
Image int64 `help:"Template count" json:"image,omitzero"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
type QuotaOptions struct {
|
||||
Tenant string `help:"Tenant name of ID"`
|
||||
User string `help:"User name of ID"`
|
||||
}
|
||||
R(&QuotaOptions{}, "quota", "Show quota for current user or tenant", func(s *mcclient.ClientSession, args *QuotaOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
if len(args.Tenant) > 0 {
|
||||
params.Add(jsonutils.NewString(args.Tenant), "tenant")
|
||||
}
|
||||
if len(args.User) > 0 {
|
||||
params.Add(jsonutils.NewString(args.User), "user")
|
||||
}
|
||||
params := jsonutils.Marshal(args)
|
||||
result, err := modules.Quotas.GetQuota(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -78,20 +36,22 @@ func init() {
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
R(&QuotaOptions{}, "image-quota", "Show image quota for current user or tenant", func(s *mcclient.ClientSession, args *QuotaOptions) error {
|
||||
params := jsonutils.Marshal(args)
|
||||
result, err := modules.ImageQuotas.GetQuota(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
|
||||
type QuotaSetOptions struct {
|
||||
Tenant string `help:"Tenant name or ID to set quota"`
|
||||
User string `help:"User name of ID"`
|
||||
Tenant string `help:"Tenant name or ID to set quota" json:"tenant,omitempty"`
|
||||
QuotaBaseOptions
|
||||
}
|
||||
R(&QuotaSetOptions{}, "quota-set", "Set quota for tenant", func(s *mcclient.ClientSession, args *QuotaSetOptions) error {
|
||||
params := quotaArgs2Params(&args.QuotaBaseOptions)
|
||||
if len(args.Tenant) > 0 {
|
||||
params.Add(jsonutils.NewString(args.Tenant), "tenant")
|
||||
}
|
||||
if len(args.User) > 0 {
|
||||
params.Add(jsonutils.NewString(args.User), "user")
|
||||
}
|
||||
params := jsonutils.Marshal(args)
|
||||
result, e := modules.Quotas.DoQuotaSet(s, params)
|
||||
if e != nil {
|
||||
return e
|
||||
@@ -100,13 +60,26 @@ func init() {
|
||||
return nil
|
||||
})
|
||||
|
||||
type ImageQuotaSetOptions struct {
|
||||
Tenant string `help:"Tenant name or ID to set quota" json:"tenant,omitempty"`
|
||||
ImageQuotaBaseOptions
|
||||
}
|
||||
R(&ImageQuotaSetOptions{}, "image-quota-set", "Set image quota for tenant", func(s *mcclient.ClientSession, args *ImageQuotaSetOptions) error {
|
||||
params := jsonutils.Marshal(args)
|
||||
result, e := modules.ImageQuotas.DoQuotaSet(s, params)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
|
||||
type QuotaCheckOptions struct {
|
||||
TENANT string `help:"Tenant name or ID to check quota"`
|
||||
TENANT string `help:"Tenant name or ID to check quota" json:"tenant,omitempty"`
|
||||
QuotaBaseOptions
|
||||
}
|
||||
R(&QuotaCheckOptions{}, "quota-check", "Check quota for tenant", func(s *mcclient.ClientSession, args *QuotaCheckOptions) error {
|
||||
params := quotaArgs2Params(&args.QuotaBaseOptions)
|
||||
params.Add(jsonutils.NewString(args.TENANT), "tenant")
|
||||
params := jsonutils.Marshal(args)
|
||||
result, e := modules.Quotas.DoQuotaCheck(s, params)
|
||||
if e != nil {
|
||||
return e
|
||||
@@ -115,4 +88,18 @@ func init() {
|
||||
return nil
|
||||
})
|
||||
|
||||
type ImageQuotaCheckOptions struct {
|
||||
TENANT string `help:"Tenant name or ID to check quota" json:"tenant,omitempty"`
|
||||
ImageQuotaBaseOptions
|
||||
}
|
||||
R(&ImageQuotaCheckOptions{}, "image-quota-check", "Check quota for tenant", func(s *mcclient.ClientSession, args *ImageQuotaCheckOptions) error {
|
||||
params := jsonutils.Marshal(args)
|
||||
result, e := modules.ImageQuotas.DoQuotaCheck(s, params)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -112,4 +112,20 @@ func init() {
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
|
||||
type ImageUsageOptions struct {
|
||||
Project string `help:"check image usage of a project"`
|
||||
}
|
||||
R(&ImageUsageOptions{}, "image-usage", "Show general usage of images", func(s *mcclient.ClientSession, args *ImageUsageOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
if args.Project != "" {
|
||||
params.Add(jsonutils.NewString(args.Project), "project")
|
||||
}
|
||||
result, err := modules.ImageUsages.GetUsage(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
9
cmd/glance/main.go
Normal file
9
cmd/glance/main.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/image/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
service.StartService()
|
||||
}
|
||||
140
cmd/torrent/main.go
Normal file
140
cmd/torrent/main.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"os/signal"
|
||||
"path"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/anacrolix/torrent"
|
||||
"github.com/anacrolix/torrent/metainfo"
|
||||
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/structarg"
|
||||
"yunion.io/x/pkg/util/version"
|
||||
"yunion.io/x/onecloud/pkg/util/torrentutils"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
structarg.BaseOptions
|
||||
|
||||
ROOT string `help:"Root directory to seed files"`
|
||||
TORRENT string `help:"path to torrent file"`
|
||||
|
||||
Tracker []string `help:"Tracker urls, e.g. http://10.168.222.252:6969/announce or udp://tracker.istole.it:6969"`
|
||||
}
|
||||
|
||||
func exitSignalHandlers(client *torrent.Client) {
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
|
||||
for {
|
||||
log.Printf("close signal received: %+v", <-c)
|
||||
client.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
options := Options{}
|
||||
|
||||
parser, err := structarg.NewArgumentParser(&options, "torrent-srv", "bit-torrent server", "2018")
|
||||
if err != nil {
|
||||
log.Fatalf("%s", err)
|
||||
}
|
||||
|
||||
parser.ParseArgs(os.Args[1:], true)
|
||||
|
||||
if options.Help {
|
||||
fmt.Println(parser.HelpString())
|
||||
return
|
||||
}
|
||||
|
||||
if len(os.Args) <= 1 {
|
||||
fmt.Print(parser.Usage())
|
||||
return
|
||||
}
|
||||
|
||||
if options.Version {
|
||||
fmt.Println(version.GetJsonString())
|
||||
return
|
||||
}
|
||||
|
||||
root, err := filepath.Abs(options.ROOT)
|
||||
if err != nil {
|
||||
log.Fatalf("fail to get absolute path: %s", err)
|
||||
}
|
||||
|
||||
var mi *metainfo.MetaInfo
|
||||
|
||||
if len(options.Tracker) > 0 {
|
||||
// server mode
|
||||
mi, err = torrentutils.GenerateTorrent(root, options.Tracker, options.TORRENT)
|
||||
if err != nil {
|
||||
log.Fatalf("fail to save torrent file %s", err)
|
||||
}
|
||||
|
||||
} else {
|
||||
// client mode, load mi from torrent file
|
||||
mi, err = metainfo.LoadFromFile(options.TORRENT)
|
||||
if err != nil {
|
||||
log.Fatalf("fail to open torrent file %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
clientConfig := torrent.NewDefaultClientConfig()
|
||||
clientConfig.Debug = false
|
||||
clientConfig.Seed = true
|
||||
if len(options.Tracker) > 0 {
|
||||
// server mode
|
||||
clientConfig.DataDir = path.Dir(root)
|
||||
} else {
|
||||
// client mode
|
||||
clientConfig.DataDir = root
|
||||
}
|
||||
clientConfig.DisableTrackers = false
|
||||
clientConfig.DisablePEX = false
|
||||
clientConfig.NoDHT = true
|
||||
|
||||
client, err := torrent.NewClient(clientConfig)
|
||||
if err != nil {
|
||||
log.Fatalf("error creating client: %s", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
go exitSignalHandlers(client)
|
||||
|
||||
t, err := client.AddTorrent(mi)
|
||||
if err != nil {
|
||||
log.Fatalf("%s", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-t.GotInfo()
|
||||
|
||||
files := t.Info().Files
|
||||
log.Debugf("Got Info, start download %d files", len(files))
|
||||
for i := 0; i < len(files); i += 1 {
|
||||
log.Debugf("%d: %s", i, files[i].Path)
|
||||
}
|
||||
|
||||
t.DownloadAll()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
<-client.Closed()
|
||||
log.Debugf("client closed, exit!")
|
||||
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
for {
|
||||
if t.BytesCompleted() == t.Info().TotalLength() {
|
||||
fmt.Printf("\rSeeding.............")
|
||||
} else {
|
||||
fmt.Printf("\rDownload: %.1f%%", float64(t.BytesCompleted())*100.0/float64(t.Info().TotalLength()))
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,13 @@ type SAppParams struct {
|
||||
SkipTrace bool
|
||||
Params map[string]string
|
||||
Path []string
|
||||
Request *http.Request
|
||||
|
||||
Request *http.Request
|
||||
Response http.ResponseWriter
|
||||
|
||||
OverrideResponseBodyWrapper bool
|
||||
|
||||
Cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func AppContextGetParams(ctx context.Context) *SAppParams {
|
||||
|
||||
@@ -107,21 +107,25 @@ func (app *Application) AddReverseProxyHandler(prefix string, ef *proxy.SEndpoin
|
||||
}
|
||||
}
|
||||
|
||||
func (app *Application) AddHandler(method string, prefix string, handler func(context.Context, http.ResponseWriter, *http.Request)) {
|
||||
app.AddHandler2(method, prefix, handler, nil, "", nil)
|
||||
func (app *Application) AddHandler(method string, prefix string,
|
||||
handler func(context.Context, http.ResponseWriter, *http.Request)) *SHandlerInfo {
|
||||
return app.AddHandler2(method, prefix, handler, nil, "", nil)
|
||||
}
|
||||
|
||||
func (app *Application) AddHandler2(method string, prefix string, handler func(context.Context, http.ResponseWriter, *http.Request), metadata map[string]interface{}, name string, tags map[string]string) {
|
||||
func (app *Application) AddHandler2(method string, prefix string,
|
||||
handler func(context.Context, http.ResponseWriter, *http.Request),
|
||||
metadata map[string]interface{}, name string, tags map[string]string) *SHandlerInfo {
|
||||
segs := SplitPath(prefix)
|
||||
hi := newHandlerInfo(method, segs, handler, metadata, name, tags)
|
||||
app.AddHandler3(hi)
|
||||
return app.AddHandler3(hi)
|
||||
}
|
||||
|
||||
func (app *Application) AddHandler3(hi *SHandlerInfo) {
|
||||
func (app *Application) AddHandler3(hi *SHandlerInfo) *SHandlerInfo {
|
||||
e := app.getRoot(hi.method).Add(hi.path, hi)
|
||||
if e != nil {
|
||||
log.Fatalf("Fail to register %s %s: %s", hi.method, hi.path, e)
|
||||
}
|
||||
return hi
|
||||
}
|
||||
|
||||
type loggingResponseWriter struct {
|
||||
@@ -220,6 +224,8 @@ func (app *Application) defaultHandle(w http.ResponseWriter, r *http.Request, ri
|
||||
}
|
||||
appParams := hand.GetAppParams(params, segs)
|
||||
appParams.Request = r
|
||||
appParams.Response = w
|
||||
appParams.Cancel = cancel
|
||||
session.Run(
|
||||
func() {
|
||||
if ctx.Err() == nil {
|
||||
@@ -336,17 +342,26 @@ func (app *Application) ListenAndServeTLS(addr string, certFile, keyFile string)
|
||||
}
|
||||
}
|
||||
|
||||
func FetchEnv(ctx context.Context, w http.ResponseWriter, r *http.Request) (map[string]string, jsonutils.JSONObject, jsonutils.JSONObject) {
|
||||
params := appctx.AppContextParams(ctx)
|
||||
query, e := jsonutils.ParseQueryString(r.URL.RawQuery)
|
||||
if e != nil {
|
||||
log.Errorf("Parse query string %s failed: %s", r.URL.RawQuery, e)
|
||||
func isJsonContentType(r *http.Request) bool {
|
||||
contType := strings.ToLower(r.Header.Get("Content-Type"))
|
||||
if strings.HasPrefix(contType, "application/json") {
|
||||
return true
|
||||
}
|
||||
var body jsonutils.JSONObject = nil
|
||||
if r.Method == "PUT" || r.Method == "POST" || r.Method == "DELETE" || r.Method == "PATCH" {
|
||||
body, e = FetchJSON(r)
|
||||
if e != nil {
|
||||
log.Errorf("Fail to decode JSON request body: %s", e)
|
||||
return false
|
||||
}
|
||||
|
||||
func FetchEnv(ctx context.Context, w http.ResponseWriter, r *http.Request) (params map[string]string, query jsonutils.JSONObject, body jsonutils.JSONObject) {
|
||||
var err error
|
||||
params = appctx.AppContextParams(ctx)
|
||||
query, err = jsonutils.ParseQueryString(r.URL.RawQuery)
|
||||
if err != nil {
|
||||
log.Errorf("Parse query string %s failed: %s", r.URL.RawQuery, err)
|
||||
}
|
||||
//var body jsonutils.JSONObject = nil
|
||||
if (r.Method == "PUT" || r.Method == "POST" || r.Method == "DELETE" || r.Method == "PATCH") && r.ContentLength > 0 && isJsonContentType(r) {
|
||||
body, err = FetchJSON(r)
|
||||
if err != nil {
|
||||
log.Errorf("Fail to decode JSON request body: %s", err)
|
||||
}
|
||||
}
|
||||
return params, query, body
|
||||
|
||||
@@ -19,66 +19,82 @@ func AddModelDispatcher(prefix string, app *appsrv.Application, manager IModelDi
|
||||
metadata := map[string]interface{}{"manager": manager}
|
||||
tags := map[string]string{"resource": manager.KeywordPlural()}
|
||||
// list
|
||||
app.AddHandler2("GET",
|
||||
h := app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/%s", prefix, manager.KeywordPlural()),
|
||||
manager.Filter(listHandler), metadata, "list", tags)
|
||||
manager.CustomizeHandlerInfo(h)
|
||||
|
||||
ctxs := manager.ContextKeywordPlural()
|
||||
// list in context
|
||||
if ctxs != nil && len(ctxs) > 0 {
|
||||
for _, ctx := range ctxs {
|
||||
app.AddHandler2("GET",
|
||||
h = app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/%s/<resid>/%s", prefix, ctx, manager.KeywordPlural()),
|
||||
manager.Filter(listInContextHandler), metadata, "list", tags)
|
||||
manager.Filter(listInContextHandler), metadata, fmt.Sprintf("list_in_%s", ctx), tags)
|
||||
manager.CustomizeHandlerInfo(h)
|
||||
}
|
||||
}
|
||||
// Head
|
||||
h = app.AddHandler2("HEAD",
|
||||
fmt.Sprintf("%s/%s/<resid>", prefix, manager.KeywordPlural()),
|
||||
manager.Filter(headHandler), metadata, "head_details", tags)
|
||||
manager.CustomizeHandlerInfo(h)
|
||||
// Get
|
||||
app.AddHandler2("GET",
|
||||
h = app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/%s/<resid>", prefix, manager.KeywordPlural()),
|
||||
manager.Filter(getHandler), metadata, "get_details", tags)
|
||||
manager.CustomizeHandlerInfo(h)
|
||||
// get spec
|
||||
app.AddHandler2("GET",
|
||||
h = app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/%s/<resid>/<spec>", prefix, manager.KeywordPlural()),
|
||||
manager.Filter(getSpecHandler), metadata, "get_specific", tags)
|
||||
manager.CustomizeHandlerInfo(h)
|
||||
// create
|
||||
// create multi
|
||||
app.AddHandler2("POST",
|
||||
h = app.AddHandler2("POST",
|
||||
fmt.Sprintf("%s/%s", prefix, manager.KeywordPlural()),
|
||||
manager.Filter(createHandler), metadata, "create", tags)
|
||||
manager.CustomizeHandlerInfo(h)
|
||||
// create in context
|
||||
if ctxs != nil && len(ctxs) > 0 {
|
||||
for _, ctx := range ctxs {
|
||||
app.AddHandler2("POST",
|
||||
h = app.AddHandler2("POST",
|
||||
fmt.Sprintf("%s/%s/<resid>/%s", prefix, ctx, manager.KeywordPlural()),
|
||||
manager.Filter(createInContextHandler), metadata, "create", tags)
|
||||
manager.Filter(createInContextHandler), metadata, fmt.Sprintf("create_in_%s", ctx), tags)
|
||||
manager.CustomizeHandlerInfo(h)
|
||||
}
|
||||
}
|
||||
// batchPerformAction
|
||||
app.AddHandler2("POST",
|
||||
h = app.AddHandler2("POST",
|
||||
fmt.Sprintf("%s/%s/<action>", prefix, manager.KeywordPlural()),
|
||||
manager.Filter(performClassActionHandler), metadata, "perform_class_action", tags)
|
||||
manager.CustomizeHandlerInfo(h)
|
||||
// performAction
|
||||
// create in context
|
||||
app.AddHandler2("POST",
|
||||
h = app.AddHandler2("POST",
|
||||
fmt.Sprintf("%s/%s/<resid>/<action>", prefix, manager.KeywordPlural()),
|
||||
manager.Filter(performActionHandler), metadata, "perform_action", tags)
|
||||
manager.CustomizeHandlerInfo(h)
|
||||
// batchUpdate
|
||||
/* app.AddHandler2("PUT",
|
||||
fmt.Sprintf("%s/%s", prefix, manager.KeywordPlural()),
|
||||
manager.Filter(updateClassHandler), metadata, "update_class", tags)
|
||||
*/
|
||||
// update
|
||||
app.AddHandler2("PUT",
|
||||
h = app.AddHandler2("PUT",
|
||||
fmt.Sprintf("%s/%s/<resid>", prefix, manager.KeywordPlural()),
|
||||
manager.Filter(updateHandler), metadata, "update", tags)
|
||||
manager.CustomizeHandlerInfo(h)
|
||||
// batch Delete
|
||||
/* app.AddHandler2("DELTE",
|
||||
fmt.Sprintf("%s/%s", prefix, manager.KeywordPlural()),
|
||||
manager.Filter(batachDeleteHandler), metadata, "batch_delete", tags)
|
||||
*/
|
||||
// Delete
|
||||
app.AddHandler2("DELETE",
|
||||
h = app.AddHandler2("DELETE",
|
||||
fmt.Sprintf("%s/%s/<resid>", prefix, manager.KeywordPlural()),
|
||||
manager.Filter(deleteHandler), metadata, "delete", tags)
|
||||
manager.CustomizeHandlerInfo(h)
|
||||
}
|
||||
|
||||
func fetchEnv(ctx context.Context, w http.ResponseWriter, r *http.Request) (IModelDispatchHandler, map[string]string, jsonutils.JSONObject, jsonutils.JSONObject) {
|
||||
@@ -134,14 +150,38 @@ func wrapBody(body jsonutils.JSONObject, key string) jsonutils.JSONObject {
|
||||
}
|
||||
}
|
||||
|
||||
func headHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
w.Header().Set("Content-Length", "0")
|
||||
w.Write([]byte{})
|
||||
}()
|
||||
|
||||
manager, params, query, _ := fetchEnv(ctx, w, r)
|
||||
_, err := manager.Get(ctx, params["<resid>"], mergeQueryParams(params, query, "<resid>"), true)
|
||||
if err != nil {
|
||||
jsonErr := httperrors.NewGeneralError(err)
|
||||
httperrors.SendHTTPErrorHeader(w, jsonErr.Code)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func getHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, params, query, _ := fetchEnv(ctx, w, r)
|
||||
result, err := manager.Get(ctx, params["<resid>"], mergeQueryParams(params, query, "<resid>"))
|
||||
result, err := manager.Get(ctx, params["<resid>"], mergeQueryParams(params, query, "<resid>"), false)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(w, err)
|
||||
return
|
||||
}
|
||||
appsrv.SendJSON(w, wrapBody(result, manager.Keyword()))
|
||||
if result != nil {
|
||||
appParams := appsrv.AppContextGetParams(ctx)
|
||||
var body jsonutils.JSONObject
|
||||
if appParams != nil && appParams.OverrideResponseBodyWrapper {
|
||||
body = result
|
||||
} else {
|
||||
body = wrapBody(result, manager.Keyword())
|
||||
}
|
||||
appsrv.SendJSON(w, body)
|
||||
}
|
||||
}
|
||||
|
||||
func getSpecHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
@@ -151,21 +191,35 @@ func getSpecHandler(ctx context.Context, w http.ResponseWriter, r *http.Request)
|
||||
httperrors.GeneralServerError(w, err)
|
||||
return
|
||||
}
|
||||
appsrv.SendJSON(w, wrapBody(result, manager.Keyword()))
|
||||
if result != nil {
|
||||
appsrv.SendJSON(w, wrapBody(result, manager.Keyword()))
|
||||
}
|
||||
}
|
||||
|
||||
func createHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, params, query, body := fetchEnv(ctx, w, r)
|
||||
handleCreate(ctx, w, manager, "", mergeQueryParams(params, query), body)
|
||||
handleCreate(ctx, w, manager, "", mergeQueryParams(params, query), body, r)
|
||||
}
|
||||
|
||||
func handleCreate(ctx context.Context, w http.ResponseWriter, manager IModelDispatchHandler, ctxId string, query jsonutils.JSONObject, body jsonutils.JSONObject) {
|
||||
count, _ := body.Int("count")
|
||||
data, err := body.Get(manager.Keyword())
|
||||
if err != nil {
|
||||
httperrors.InvalidInputError(w,
|
||||
fmt.Sprintf("No request key: %s", manager.Keyword()))
|
||||
return
|
||||
func handleCreate(ctx context.Context, w http.ResponseWriter, manager IModelDispatchHandler, ctxId string, query jsonutils.JSONObject, body jsonutils.JSONObject, r *http.Request) {
|
||||
count := int64(1)
|
||||
var data jsonutils.JSONObject
|
||||
var err error
|
||||
if body != nil {
|
||||
count, _ = body.Int("count")
|
||||
data, err = body.Get(manager.Keyword())
|
||||
if err != nil {
|
||||
httperrors.InvalidInputError(w,
|
||||
fmt.Sprintf("No request key: %s", manager.Keyword()))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
data, err = manager.FetchCreateHeaderData(ctx, r.Header)
|
||||
if err != nil {
|
||||
httperrors.InvalidInputError(w,
|
||||
fmt.Sprintf("In valid request header: %s", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
if count <= 1 {
|
||||
result, err := manager.Create(ctx, query, data, ctxId)
|
||||
@@ -194,7 +248,7 @@ func handleCreate(ctx context.Context, w http.ResponseWriter, manager IModelDisp
|
||||
func createInContextHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, params, query, body := fetchEnv(ctx, w, r)
|
||||
ctxId := params["<resid>"]
|
||||
handleCreate(ctx, w, manager, ctxId, mergeQueryParams(params, query, "<resid>"), body)
|
||||
handleCreate(ctx, w, manager, ctxId, mergeQueryParams(params, query, "<resid>"), body, r)
|
||||
}
|
||||
|
||||
func performClassActionHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
@@ -258,11 +312,22 @@ func updateClassHandler(ctx context.Context, w http.ResponseWriter, r *http.Requ
|
||||
|
||||
func updateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
manager, params, query, body := fetchEnv(ctx, w, r)
|
||||
data, err := body.Get(manager.Keyword())
|
||||
if err != nil {
|
||||
httperrors.InvalidInputError(w,
|
||||
fmt.Sprintf("No request key: %s", manager.Keyword()))
|
||||
return
|
||||
var data jsonutils.JSONObject
|
||||
var err error
|
||||
if body != nil {
|
||||
data, err = body.Get(manager.Keyword())
|
||||
if err != nil {
|
||||
httperrors.InvalidInputError(w,
|
||||
fmt.Sprintf("No request key: %s", manager.Keyword()))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
data, err = manager.FetchUpdateHeaderData(ctx, r.Header)
|
||||
if err != nil {
|
||||
httperrors.InvalidInputError(w,
|
||||
fmt.Sprintf("In valid request header: %s", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
result, err := manager.Update(ctx, params["<resid>"], mergeQueryParams(params, query, "<resid>"), data)
|
||||
if err != nil {
|
||||
|
||||
@@ -3,6 +3,7 @@ package dispatcher
|
||||
import (
|
||||
"context"
|
||||
|
||||
"net/http"
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
@@ -20,7 +21,7 @@ type IModelDispatchHandler interface {
|
||||
ContextKeywordPlural() []string
|
||||
|
||||
List(ctx context.Context, query jsonutils.JSONObject, ctxId string) (*modules.ListResult, error)
|
||||
Get(ctx context.Context, idstr string, query jsonutils.JSONObject) (jsonutils.JSONObject, error)
|
||||
Get(ctx context.Context, idstr string, query jsonutils.JSONObject, isHead bool) (jsonutils.JSONObject, error)
|
||||
GetSpecific(ctx context.Context, idstr string, spec string, query jsonutils.JSONObject) (jsonutils.JSONObject, error)
|
||||
Create(ctx context.Context, query jsonutils.JSONObject, data jsonutils.JSONObject, ctxId string) (jsonutils.JSONObject, error)
|
||||
BatchCreate(ctx context.Context, query jsonutils.JSONObject, data jsonutils.JSONObject, count int, ctxId string) ([]modules.SubmitResult, error)
|
||||
@@ -30,6 +31,10 @@ type IModelDispatchHandler interface {
|
||||
Update(ctx context.Context, idstr string, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error)
|
||||
// DeleteClass(ctx context.Context, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error)
|
||||
Delete(ctx context.Context, idstr string, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error)
|
||||
|
||||
CustomizeHandlerInfo(info *appsrv.SHandlerInfo)
|
||||
FetchCreateHeaderData(ctx context.Context, header http.Header) (jsonutils.JSONObject, error)
|
||||
FetchUpdateHeaderData(ctx context.Context, header http.Header) (jsonutils.JSONObject, error)
|
||||
}
|
||||
|
||||
type IJointModelDispatchHandler interface {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"yunion.io/x/pkg/utils"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
"net/http"
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/consts"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
|
||||
@@ -64,6 +65,10 @@ func (dispatcher *DBModelDispatcher) Filter(f appsrv.FilterHandler) appsrv.Filte
|
||||
}
|
||||
}
|
||||
|
||||
func (dispatcher *DBModelDispatcher) CustomizeHandlerInfo(handler *appsrv.SHandlerInfo) {
|
||||
dispatcher.modelManager.CustomizeHandlerInfo(handler)
|
||||
}
|
||||
|
||||
func fetchUserCredential(ctx context.Context) mcclient.TokenCredential {
|
||||
token := auth.FetchUserCredential(ctx, policy.FilterPolicyCredential)
|
||||
if token == nil && !consts.IsRbacEnabled() {
|
||||
@@ -130,7 +135,7 @@ func searchFields(manager IModelManager, userCred mcclient.TokenCredential) []st
|
||||
return ret
|
||||
}
|
||||
|
||||
func getDetailFields(manager IModelManager, userCred mcclient.TokenCredential) []string {
|
||||
func GetDetailFields(manager IModelManager, userCred mcclient.TokenCredential) []string {
|
||||
ret := make([]string, 0)
|
||||
for _, col := range manager.TableSpec().Columns() {
|
||||
tags := col.Tags()
|
||||
@@ -407,7 +412,7 @@ func fetchContextObjectId(manager IModelManager, ctx context.Context, userCred m
|
||||
}
|
||||
}
|
||||
|
||||
func listItems(manager IModelManager, ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, ctxId string) (*modules.ListResult, error) {
|
||||
func ListItems(manager IModelManager, ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, ctxId string) (*modules.ListResult, error) {
|
||||
var maxLimit int64 = 2048
|
||||
limit, _ := query.Int("limit")
|
||||
offset, _ := query.Int("offset")
|
||||
@@ -539,7 +544,7 @@ func (dispatcher *DBModelDispatcher) List(ctx context.Context, query jsonutils.J
|
||||
return nil, httperrors.NewForbiddenError("Not allow to list")
|
||||
}
|
||||
|
||||
items, err := listItems(dispatcher.modelManager, ctx, userCred, query, ctxId)
|
||||
items, err := ListItems(dispatcher.modelManager, ctx, userCred, query, ctxId)
|
||||
if err != nil {
|
||||
log.Errorf("Fail to list items: %s", err)
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
@@ -563,19 +568,48 @@ func getModelExtraDetails(item IModel, ctx context.Context, extra *jsonutils.JSO
|
||||
return extra
|
||||
}
|
||||
|
||||
func getModelItemDetails(manager IModelManager, item IModel, ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, isHead bool) (jsonutils.JSONObject, error) {
|
||||
appParams := appsrv.AppContextGetParams(ctx)
|
||||
if appParams == nil && isHead {
|
||||
log.Errorf("fail to get http response writer???")
|
||||
return nil, httperrors.NewInternalServerError("fail to get http response writer from context")
|
||||
}
|
||||
hdrs := item.GetExtraDetailsHeaders(ctx, userCred, query)
|
||||
for k, v := range hdrs {
|
||||
appParams.Response.Header().Add(k, v)
|
||||
}
|
||||
|
||||
if isHead {
|
||||
appParams.Response.Header().Add("Content-Length", "0")
|
||||
appParams.Response.Write([]byte{})
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if manager.IsCustomizedGetDetailsBody() {
|
||||
return item.CustomizedGetDetailsBody(ctx, userCred, query)
|
||||
} else {
|
||||
return getItemDetails(manager, item, ctx, userCred, query)
|
||||
}
|
||||
}
|
||||
|
||||
func getItemDetails(manager IModelManager, item IModel, ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
jsonData := jsonutils.Marshal(item)
|
||||
jsonDict, ok := jsonData.(*jsonutils.JSONDict)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("fail to convert model to json")
|
||||
}
|
||||
jsonDict = jsonDict.CopyIncludes(getDetailFields(manager, userCred)...)
|
||||
extraDict := item.GetExtraDetails(ctx, userCred, query)
|
||||
if extraDict != nil {
|
||||
extraDict, err := item.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
} else if extraDict != nil {
|
||||
jsonData := jsonutils.Marshal(item)
|
||||
jsonDict, ok := jsonData.(*jsonutils.JSONDict)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("fail to convert model to json")
|
||||
}
|
||||
jsonDict = jsonDict.CopyIncludes(GetDetailFields(manager, userCred)...)
|
||||
jsonDict.Update(extraDict)
|
||||
jsonDict = getModelExtraDetails(item, ctx, jsonDict)
|
||||
return jsonDict, nil
|
||||
} else {
|
||||
// override GetExtraDetails
|
||||
return nil, nil
|
||||
}
|
||||
jsonDict = getModelExtraDetails(item, ctx, jsonDict)
|
||||
return jsonDict, nil
|
||||
}
|
||||
|
||||
func (dispatcher *DBModelDispatcher) tryGetModelProperty(ctx context.Context, property string, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
@@ -620,7 +654,7 @@ func (dispatcher *DBModelDispatcher) tryGetModelProperty(ctx context.Context, pr
|
||||
}
|
||||
}
|
||||
|
||||
func (dispatcher *DBModelDispatcher) Get(ctx context.Context, idStr string, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
func (dispatcher *DBModelDispatcher) Get(ctx context.Context, idStr string, query jsonutils.JSONObject, isHead bool) (jsonutils.JSONObject, error) {
|
||||
// log.Debugf("Get %s", idStr)
|
||||
userCred := fetchUserCredential(ctx)
|
||||
|
||||
@@ -647,7 +681,7 @@ func (dispatcher *DBModelDispatcher) Get(ctx context.Context, idStr string, quer
|
||||
if !isAllow {
|
||||
return nil, httperrors.NewForbiddenError("Not allow to get details")
|
||||
}
|
||||
return getItemDetails(dispatcher.modelManager, model, ctx, userCred, query)
|
||||
return getModelItemDetails(dispatcher.modelManager, model, ctx, userCred, query, isHead)
|
||||
}
|
||||
|
||||
func (dispatcher *DBModelDispatcher) GetSpecific(ctx context.Context, idStr string, spec string, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
@@ -820,10 +854,13 @@ func doCreateItem(manager IModelManager, ctx context.Context, userCred mcclient.
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
model.PostCreate(ctx, userCred, ownerProjId, query, dataDict)
|
||||
return model, nil
|
||||
}
|
||||
|
||||
func (dispatcher *DBModelDispatcher) FetchCreateHeaderData(ctx context.Context, header http.Header) (jsonutils.JSONObject, error) {
|
||||
return dispatcher.modelManager.FetchCreateHeaderData(ctx, header)
|
||||
}
|
||||
|
||||
func (dispatcher *DBModelDispatcher) Create(ctx context.Context, query jsonutils.JSONObject, data jsonutils.JSONObject, ctxId string) (jsonutils.JSONObject, error) {
|
||||
userCred := fetchUserCredential(ctx)
|
||||
|
||||
@@ -845,9 +882,6 @@ func (dispatcher *DBModelDispatcher) Create(ctx context.Context, query jsonutils
|
||||
}
|
||||
}
|
||||
|
||||
lockman.LockClass(ctx, dispatcher.modelManager, ownerProjId)
|
||||
defer lockman.ReleaseClass(ctx, dispatcher.modelManager, ownerProjId)
|
||||
|
||||
var isAllow bool
|
||||
if consts.IsRbacEnabled() {
|
||||
isAllow = isClassActionRbacAllowed(dispatcher.modelManager, userCred, ownerProjId, policy.PolicyActionCreate)
|
||||
@@ -858,11 +892,23 @@ func (dispatcher *DBModelDispatcher) Create(ctx context.Context, query jsonutils
|
||||
return nil, httperrors.NewForbiddenError("Not allow to create item")
|
||||
}
|
||||
|
||||
model, err := doCreateItem(dispatcher.modelManager, ctx, userCred, ownerProjId, query, data)
|
||||
model, err := func() (IModel, error) {
|
||||
lockman.LockClass(ctx, dispatcher.modelManager, ownerProjId)
|
||||
defer lockman.ReleaseClass(ctx, dispatcher.modelManager, ownerProjId)
|
||||
|
||||
return doCreateItem(dispatcher.modelManager, ctx, userCred, ownerProjId, query, data)
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("fail to doCreateItem %s", err)
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
|
||||
lockman.LockObject(ctx, model)
|
||||
defer lockman.ReleaseObject(ctx, model)
|
||||
|
||||
model.PostCreate(ctx, userCred, ownerProjId, query, data)
|
||||
|
||||
OpsLog.LogEvent(model, ACT_CREATE, model.GetShortDesc(ctx), userCred)
|
||||
logclient.AddActionLog(model, logclient.ACT_CREATE, "", userCred, true)
|
||||
dispatcher.modelManager.OnCreateComplete(ctx, []IModel{model}, userCred, query, data)
|
||||
@@ -909,9 +955,6 @@ func (dispatcher *DBModelDispatcher) BatchCreate(ctx context.Context, query json
|
||||
}
|
||||
}
|
||||
|
||||
lockman.LockClass(ctx, dispatcher.modelManager, ownerProjId)
|
||||
defer lockman.ReleaseClass(ctx, dispatcher.modelManager, ownerProjId)
|
||||
|
||||
var isAllow bool
|
||||
if consts.IsRbacEnabled() {
|
||||
isAllow = isClassActionRbacAllowed(dispatcher.modelManager, userCred, ownerProjId, policy.PolicyActionCreate)
|
||||
@@ -922,27 +965,53 @@ func (dispatcher *DBModelDispatcher) BatchCreate(ctx context.Context, query json
|
||||
return nil, httperrors.NewForbiddenError("Not allow to create item")
|
||||
}
|
||||
|
||||
multiData, err := expandMultiCreateParams(data, count)
|
||||
type sCreateResult struct {
|
||||
model IModel
|
||||
err error
|
||||
}
|
||||
|
||||
var multiData []jsonutils.JSONObject
|
||||
|
||||
createResults, err := func() ([]sCreateResult, error) {
|
||||
lockman.LockClass(ctx, dispatcher.modelManager, ownerProjId)
|
||||
defer lockman.ReleaseClass(ctx, dispatcher.modelManager, ownerProjId)
|
||||
|
||||
multiData, err = expandMultiCreateParams(data, count)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := make([]sCreateResult, len(multiData))
|
||||
for i, cdata := range multiData {
|
||||
model, err := doCreateItem(dispatcher.modelManager, ctx, userCred, ownerProjId, query, cdata)
|
||||
ret[i] = sCreateResult{model: model, err: err}
|
||||
}
|
||||
return ret, nil
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results := make([]modules.SubmitResult, count)
|
||||
models := make([]IModel, 0)
|
||||
for i, cdata := range multiData {
|
||||
model, err := doCreateItem(dispatcher.modelManager, ctx, userCred, ownerProjId, query, cdata)
|
||||
for i, res := range createResults {
|
||||
result := modules.SubmitResult{}
|
||||
if err != nil {
|
||||
jsonErr, ok := err.(*httputils.JSONClientError)
|
||||
if res.err != nil {
|
||||
jsonErr, ok := res.err.(*httputils.JSONClientError)
|
||||
if ok {
|
||||
result.Status = jsonErr.Code
|
||||
result.Data = jsonutils.Marshal(jsonErr)
|
||||
} else {
|
||||
result.Status = 500
|
||||
result.Data = jsonutils.NewString(err.Error())
|
||||
result.Data = jsonutils.NewString(res.err.Error())
|
||||
}
|
||||
} else {
|
||||
models = append(models, model)
|
||||
body, err := getItemDetails(dispatcher.modelManager, model, ctx, userCred, query)
|
||||
lockman.LockObject(ctx, res.model)
|
||||
defer lockman.ReleaseObject(ctx, res.model)
|
||||
|
||||
res.model.PostCreate(ctx, userCred, ownerProjId, query, data)
|
||||
|
||||
models = append(models, res.model)
|
||||
body, err := getItemDetails(dispatcher.modelManager, res.model, ctx, userCred, query)
|
||||
if err != nil {
|
||||
result.Status = 500
|
||||
result.Data = jsonutils.NewString(err.Error())
|
||||
@@ -954,6 +1023,9 @@ func (dispatcher *DBModelDispatcher) BatchCreate(ctx context.Context, query json
|
||||
results[i] = result
|
||||
}
|
||||
if len(models) > 0 {
|
||||
lockman.LockClass(ctx, dispatcher.modelManager, ownerProjId)
|
||||
defer lockman.ReleaseClass(ctx, dispatcher.modelManager, ownerProjId)
|
||||
|
||||
dispatcher.modelManager.OnCreateComplete(ctx, models, userCred, query, multiData[0])
|
||||
}
|
||||
return results, nil
|
||||
@@ -1145,7 +1217,9 @@ func updateItem(manager IModelManager, item IModel, ctx context.Context, userCre
|
||||
logclient.AddActionLog(item, logclient.ACT_UPDATE, errMsg, userCred, false)
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
|
||||
item.PreUpdate(ctx, userCred, query, dataDict)
|
||||
|
||||
diff, err := manager.TableSpec().Update(item, func() error {
|
||||
filterData := dataDict.CopyIncludes(updateFields(manager, userCred)...)
|
||||
err = filterData.Unmarshal(item)
|
||||
@@ -1172,9 +1246,16 @@ func updateItem(manager IModelManager, item IModel, ctx context.Context, userCre
|
||||
} else {
|
||||
logclient.AddActionLog(item, logclient.ACT_UPDATE, "", userCred, true)
|
||||
}
|
||||
|
||||
item.PostUpdate(ctx, userCred, query, data)
|
||||
|
||||
return getItemDetails(manager, item, ctx, userCred, query)
|
||||
}
|
||||
|
||||
func (dispatcher *DBModelDispatcher) FetchUpdateHeaderData(ctx context.Context, header http.Header) (jsonutils.JSONObject, error) {
|
||||
return dispatcher.modelManager.FetchUpdateHeaderData(ctx, header)
|
||||
}
|
||||
|
||||
func (dispatcher *DBModelDispatcher) Update(ctx context.Context, idStr string, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
userCred := fetchUserCredential(ctx)
|
||||
model, err := fetchItem(dispatcher.modelManager, ctx, userCred, idStr, nil)
|
||||
|
||||
@@ -109,7 +109,7 @@ func (dispatcher *DBJointModelDispatcher) _listJoint(ctx context.Context, userCr
|
||||
return nil, httperrors.NewForbiddenError("Not allow to list")
|
||||
}
|
||||
|
||||
items, err := listItems(dispatcher.JointModelManager(), ctx, userCred, queryDict, "")
|
||||
items, err := ListItems(dispatcher.JointModelManager(), ctx, userCred, queryDict, "")
|
||||
if err != nil {
|
||||
log.Errorf("Fail to list items: %s", err)
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
"net/http"
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
|
||||
)
|
||||
|
||||
@@ -63,6 +65,11 @@ type IModelManager interface {
|
||||
DoCreate(ctx context.Context, userCred mcclient.TokenCredential, kwargs jsonutils.JSONObject, data jsonutils.JSONObject, realManager IModelManager) (IModel, error)
|
||||
|
||||
InitializeData() error
|
||||
|
||||
CustomizeHandlerInfo(info *appsrv.SHandlerInfo)
|
||||
FetchCreateHeaderData(ctx context.Context, header http.Header) (jsonutils.JSONObject, error)
|
||||
FetchUpdateHeaderData(ctx context.Context, header http.Header) (jsonutils.JSONObject, error)
|
||||
IsCustomizedGetDetailsBody() bool
|
||||
}
|
||||
|
||||
type IModel interface {
|
||||
@@ -82,7 +89,8 @@ type IModel interface {
|
||||
|
||||
// get hooks
|
||||
AllowGetDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool
|
||||
GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict
|
||||
GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error)
|
||||
GetExtraDetailsHeaders(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) map[string]string
|
||||
|
||||
// create hooks
|
||||
CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data jsonutils.JSONObject) error
|
||||
@@ -110,6 +118,8 @@ type IModel interface {
|
||||
PostDelete(ctx context.Context, userCred mcclient.TokenCredential)
|
||||
|
||||
GetOwnerProjectId() string
|
||||
|
||||
CustomizedGetDetailsBody(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error)
|
||||
}
|
||||
|
||||
type IResourceModelManager interface {
|
||||
|
||||
@@ -5,7 +5,9 @@ import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"net/http"
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/sqlchemy"
|
||||
)
|
||||
@@ -170,6 +172,22 @@ func (manager *SModelBaseManager) GetExportExtraKeys(ctx context.Context, query
|
||||
return jsonutils.NewDict()
|
||||
}
|
||||
|
||||
func (manager *SModelBaseManager) CustomizeHandlerInfo(info *appsrv.SHandlerInfo) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
func (manager *SModelBaseManager) FetchCreateHeaderData(ctx context.Context, header http.Header) (jsonutils.JSONObject, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (manager *SModelBaseManager) FetchUpdateHeaderData(ctx context.Context, header http.Header) (jsonutils.JSONObject, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (manager *SModelBaseManager) IsCustomizedGetDetailsBody() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (model *SModelBase) GetId() string {
|
||||
return ""
|
||||
}
|
||||
@@ -210,8 +228,12 @@ func (model *SModelBase) AllowGetDetails(ctx context.Context, userCred mcclient.
|
||||
return false
|
||||
}
|
||||
|
||||
func (model *SModelBase) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
return jsonutils.NewDict()
|
||||
func (model *SModelBase) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
return jsonutils.NewDict(), nil
|
||||
}
|
||||
|
||||
func (model *SModelBase) GetExtraDetailsHeaders(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) map[string]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// create hooks
|
||||
@@ -286,3 +308,7 @@ func (model *SModelBase) Delete(ctx context.Context, userCred mcclient.TokenCred
|
||||
func (model *SModelBase) GetOwnerProjectId() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (model *SModelBase) CustomizedGetDetailsBody(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -25,23 +25,23 @@ func AddQuotaHandler(manager *SQuotaManager, prefix string, app *appsrv.Applicat
|
||||
_manager = manager
|
||||
|
||||
app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/quotas", prefix),
|
||||
fmt.Sprintf("%s/%s", prefix, _manager.Keyword()),
|
||||
auth.Authenticate(getQuotaHanlder), nil, "get_quota", nil)
|
||||
|
||||
app.AddHandler2("GET",
|
||||
fmt.Sprintf("%s/quotas/<tenantid>", prefix),
|
||||
fmt.Sprintf("%s/%s/<tenantid>", prefix, _manager.Keyword()),
|
||||
auth.Authenticate(getQuotaHanlder), nil, "get_quota", nil)
|
||||
|
||||
app.AddHandler2("POST",
|
||||
fmt.Sprintf("%s/quotas", prefix),
|
||||
fmt.Sprintf("%s/%s", prefix, _manager.Keyword()),
|
||||
auth.Authenticate(setQuotaHanlder), nil, "set_quota", nil)
|
||||
|
||||
app.AddHandler2("POST",
|
||||
fmt.Sprintf("%s/quotas/<tenantid>", prefix),
|
||||
fmt.Sprintf("%s/%s/<tenantid>", prefix, _manager.Keyword()),
|
||||
auth.Authenticate(setQuotaHanlder), nil, "set_quota", nil)
|
||||
|
||||
app.AddHandler2("POST",
|
||||
fmt.Sprintf("%s/quotas/<tenantid>/<action>", prefix),
|
||||
fmt.Sprintf("%s/%s/<tenantid>/<action>", prefix, _manager.Keyword()),
|
||||
auth.Authenticate(checkQuotaHanlder), nil, "check_quota", nil)
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ func getQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Request
|
||||
projectId = userCred.GetProjectId()
|
||||
if consts.IsRbacEnabled() {
|
||||
result := policy.PolicyManager.Allow(false, userCred, consts.GetServiceType(),
|
||||
"quotas", policy.PolicyActionGet)
|
||||
_manager.Keyword(), policy.PolicyActionGet)
|
||||
if result == rbacutils.Deny {
|
||||
httperrors.ForbiddenError(w, "not allow to get quota")
|
||||
return
|
||||
@@ -101,7 +101,7 @@ func getQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
if consts.IsRbacEnabled() {
|
||||
if policy.PolicyManager.Allow(true, userCred, consts.GetServiceType(),
|
||||
"quotas", policy.PolicyActionGet) != rbacutils.AdminAllow {
|
||||
_manager.Keyword(), policy.PolicyActionGet) != rbacutils.AdminAllow {
|
||||
httperrors.ForbiddenError(w, "not allow to query quota")
|
||||
return
|
||||
}
|
||||
@@ -127,7 +127,7 @@ func getQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
|
||||
body := jsonutils.NewDict()
|
||||
body.Add(quota, "quotas")
|
||||
body.Add(quota, _manager.Keyword())
|
||||
|
||||
appsrv.SendJSON(w, body)
|
||||
}
|
||||
@@ -138,10 +138,10 @@ func setQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Request
|
||||
var isAllow bool
|
||||
if consts.IsRbacEnabled() {
|
||||
isAllow = policy.PolicyManager.Allow(true, userCred, consts.GetServiceType(),
|
||||
"quotas", policy.PolicyActionUpdate) == rbacutils.AdminAllow
|
||||
_manager.Keyword(), policy.PolicyActionUpdate) == rbacutils.AdminAllow
|
||||
} else {
|
||||
isAllow = userCred.IsAdminAllow(consts.GetServiceType(),
|
||||
"quotas", policy.PolicyActionUpdate)
|
||||
_manager.Keyword(), policy.PolicyActionUpdate)
|
||||
}
|
||||
if !isAllow {
|
||||
httperrors.ForbiddenError(w, "not allow to set quota")
|
||||
@@ -171,7 +171,7 @@ func setQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Request
|
||||
return
|
||||
}
|
||||
quota := _manager.newQuota()
|
||||
err = body.Unmarshal(quota, "quotas")
|
||||
err = body.Unmarshal(quota, _manager.Keyword())
|
||||
if err != nil {
|
||||
log.Errorf("Fail to decode JSON request body: %s", err)
|
||||
httperrors.InvalidInputError(w, "fail to decode body")
|
||||
@@ -192,7 +192,7 @@ func setQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Request
|
||||
return
|
||||
}
|
||||
rbody := jsonutils.NewDict()
|
||||
rbody.Add(oquota.ToJSON(""), "quotas")
|
||||
rbody.Add(oquota.ToJSON(""), _manager.Keyword())
|
||||
appsrv.SendJSON(w, rbody)
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ func checkQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
if consts.IsRbacEnabled() {
|
||||
if policy.PolicyManager.Allow(true, userCred, consts.GetServiceType(),
|
||||
"quotas", policy.PolicyActionGet) != rbacutils.AdminAllow {
|
||||
_manager.Keyword(), policy.PolicyActionGet) != rbacutils.AdminAllow {
|
||||
httperrors.ForbiddenError(w, "not allow to query quota")
|
||||
return
|
||||
}
|
||||
@@ -238,7 +238,7 @@ func checkQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
body, err := appsrv.FetchJSON(r)
|
||||
quota := _manager.newQuota()
|
||||
err = body.Unmarshal(quota, "quotas")
|
||||
err = body.Unmarshal(quota, _manager.Keyword())
|
||||
if err != nil {
|
||||
log.Errorf("Fail to decode JSON request body: %s", err)
|
||||
httperrors.InvalidInputError(w, "fail to decode body")
|
||||
@@ -250,6 +250,6 @@ func checkQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Reque
|
||||
return
|
||||
}
|
||||
rbody := jsonutils.NewDict()
|
||||
rbody.Add(used.ToJSON(""), "quotas")
|
||||
rbody.Add(used.ToJSON(""), _manager.Keyword())
|
||||
appsrv.SendJSON(w, rbody)
|
||||
}
|
||||
|
||||
19
pkg/cloudcommon/db/quotas/utils.go
Normal file
19
pkg/cloudcommon/db/quotas/utils.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package quotas
|
||||
|
||||
import "fmt"
|
||||
|
||||
func NonNegative(val int) int {
|
||||
if val < 0 {
|
||||
return 0
|
||||
} else {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
func KeyName(prefix, name string) string {
|
||||
if len(prefix) > 0 {
|
||||
return fmt.Sprintf("%s.%s", prefix, name)
|
||||
} else {
|
||||
return name
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ type SResourceBase struct {
|
||||
UpdatedAt time.Time `nullable:"false" updated_at:"true" list:"user"`
|
||||
UpdateVersion int `default:"0" nullable:"false" auto_version:"true" list:"user"`
|
||||
DeletedAt time.Time ``
|
||||
Deleted bool `nullable:"false" default:"false"`
|
||||
Deleted bool `nullable:"false" default:"false" index:"true"`
|
||||
}
|
||||
|
||||
type SResourceBaseManager struct {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
type SSharableVirtualResourceBase struct {
|
||||
SVirtualResourceBase
|
||||
|
||||
IsPublic bool `default:"false" nullable:"false" create:"admin_optional" list:"user"`
|
||||
IsPublic bool `default:"false" nullable:"false" index:"true" create:"admin_optional" list:"user"`
|
||||
}
|
||||
|
||||
type SSharableVirtualResourceBaseManager struct {
|
||||
|
||||
@@ -5,11 +5,12 @@ import (
|
||||
"database/sql"
|
||||
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/consts"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
)
|
||||
|
||||
type STenantCacheManager struct {
|
||||
@@ -127,3 +128,14 @@ func (manager *STenantCacheManager) Save(ctx context.Context, idStr string, name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (manager *STenantCacheManager) GenerateProjectUserCred(ctx context.Context, projectName string) (mcclient.TokenCredential, error) {
|
||||
project, err := manager.FetchTenantByIdOrName(ctx, projectName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mcclient.SSimpleToken{
|
||||
Project: project.Name,
|
||||
ProjectId: project.Id,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -191,9 +191,12 @@ func (model *SVirtualResourceBase) GetCustomizeColumns(ctx context.Context, user
|
||||
return model.getMoreDetails(ctx, userCred, query, extra)
|
||||
}
|
||||
|
||||
func (model *SVirtualResourceBase) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := model.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
return model.getMoreDetails(ctx, userCred, query, extra)
|
||||
func (model *SVirtualResourceBase) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := model.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return model.getMoreDetails(ctx, userCred, query, extra), nil
|
||||
}
|
||||
|
||||
func (model *SVirtualResourceBase) AllowPerformChangeOwner(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"go.etcd.io/etcd/clientv3"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"go.etcd.io/etcd/clientv3"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/onecloud/pkg/util/seclib2"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -176,7 +176,7 @@ func (cli *SEtcdClient) Get(ctx context.Context, key string) ([]byte, error) {
|
||||
}
|
||||
|
||||
type SEtcdKeyValue struct {
|
||||
Key string
|
||||
Key string
|
||||
Value []byte
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ func (cli *SEtcdClient) List(ctx context.Context, prefix string) ([]SEtcdKeyValu
|
||||
ret := make([]SEtcdKeyValue, len(resp.Kvs))
|
||||
for i := 0; i < len(resp.Kvs); i += 1 {
|
||||
ret[i] = SEtcdKeyValue{
|
||||
Key: string(resp.Kvs[i].Key[len(cli.namespace):]),
|
||||
Key: string(resp.Kvs[i].Key[len(cli.namespace):]),
|
||||
Value: resp.Kvs[i].Value,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,17 +13,17 @@ import (
|
||||
)
|
||||
|
||||
type CommonOptions struct {
|
||||
Port int `help:"The port that the service runs on"`
|
||||
Address string `help:"The IP address to serve on (set to 0.0.0.0 for all interfaces)" default:"0.0.0.0"`
|
||||
Port int `help:"The port that the service runs on" alias:"bind-port"`
|
||||
Address string `help:"The IP address to serve on (set to 0.0.0.0 for all interfaces)" default:"0.0.0.0" alias:"bind-host"`
|
||||
|
||||
LogLevel string `help:"log level" default:"info" choices:"debug|info|warn|error"`
|
||||
LogVerboseLevel int `help:"log verbosity level" default:"0"`
|
||||
|
||||
Region string `help:"Region name or ID"`
|
||||
Region string `help:"Region name or ID" alias:"auth-region"`
|
||||
AuthURL string `help:"Keystone auth URL" alias:"auth-uri"`
|
||||
AdminUser string `help:"Admin username"`
|
||||
AdminDomain string `help:"Admin user domain"`
|
||||
AdminPassword string `help:"Admin password"`
|
||||
AdminPassword string `help:"Admin password" alias:"admin-passwd"`
|
||||
AdminProject string `help:"Admin project" default:"system" alias:"admin-tenant-name"`
|
||||
CorsHosts []string `help:"List of hostname that allow CORS"`
|
||||
AuthTokenCacheSize uint32 `help:"Auth token Cache Size" default:"2048"`
|
||||
|
||||
8
pkg/cloudcommon/pending_delete/options.go
Normal file
8
pkg/cloudcommon/pending_delete/options.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package pending_delete
|
||||
|
||||
type SPendingDeleteOptions struct {
|
||||
EnablePendingDelete bool `default:"true" help:"Turn on/off pending-delete resource, default is on" alias:"delayed_delete"`
|
||||
PendingDeleteCheckSeconds int `default:"3600" help:"How long to wait to scan pending-delete resource, default is 1 hour"`
|
||||
PendingDeleteExpireSeconds int `default:"259200" help:"How long a pending-delete resource cleaned automatically, default 3 days" alias:"scrub_time"`
|
||||
PendingDeleteMaxCleanBatchSize int `default:"50" help:"How many pending-delete items can be clean in a batch"`
|
||||
}
|
||||
20
pkg/cloudcommon/qemuimg/consts.go
Normal file
20
pkg/cloudcommon/qemuimg/consts.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package qemuimg
|
||||
|
||||
type TImageFormat string
|
||||
|
||||
const (
|
||||
QCOW2 = TImageFormat("qcow2")
|
||||
VMDK = TImageFormat("vmdk")
|
||||
VHD = TImageFormat("vhd")
|
||||
ISO = TImageFormat("iso")
|
||||
RAW = TImageFormat("raw")
|
||||
)
|
||||
|
||||
func (fmt TImageFormat) String() string {
|
||||
switch string(fmt) {
|
||||
case "vhd":
|
||||
return "vpc"
|
||||
default:
|
||||
return string(fmt)
|
||||
}
|
||||
}
|
||||
56
pkg/cloudcommon/qemuimg/init.go
Normal file
56
pkg/cloudcommon/qemuimg/init.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package qemuimg
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/qemutils"
|
||||
"regexp"
|
||||
"fmt"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/version"
|
||||
)
|
||||
|
||||
const (
|
||||
qemuImgVersionPattern = `qemu-img version (?P<ver>\d+\.\d+(\.\d+)?)`
|
||||
)
|
||||
|
||||
var (
|
||||
qemuImgVersionRegexp = regexp.MustCompile(qemuImgVersionPattern)
|
||||
|
||||
qemuImgVersion string
|
||||
)
|
||||
|
||||
func getQemuImgVersion() string {
|
||||
cmd := exec.Command(qemutils.GetQemuImg(), "--version")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Errorf("check qemu-img version fail %s", err)
|
||||
return ""
|
||||
}
|
||||
matches := qemuImgVersionRegexp.FindStringSubmatch(string(out))
|
||||
if len(matches) > 1 {
|
||||
return matches[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func QemuImgInit() error {
|
||||
ver := getQemuImgVersion()
|
||||
if len(ver) == 0 {
|
||||
return fmt.Errorf("fail to find qemu-img")
|
||||
}
|
||||
qemuImgVersion = ver
|
||||
return nil
|
||||
}
|
||||
|
||||
func qcow2SparseOptions() []string {
|
||||
if version.LE(qemuImgVersion, "1.1") {
|
||||
return []string {"preallocation=metadata", "cluster_size=2M"}
|
||||
} else if version.LE(qemuImgVersion, "1.7.1") {
|
||||
return []string {"preallocation=metadata", "lazy_refcounts=on"}
|
||||
} else if version.LE(qemuImgVersion, "2.2") {
|
||||
return []string {"preallocation=metadata", "lazy_refcounts=on", "cluster_size=2M"}
|
||||
} else {
|
||||
return []string {}
|
||||
}
|
||||
}
|
||||
417
pkg/cloudcommon/qemuimg/qemuimg.go
Normal file
417
pkg/cloudcommon/qemuimg/qemuimg.go
Normal file
@@ -0,0 +1,417 @@
|
||||
package qemuimg
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"os"
|
||||
"os/exec"
|
||||
"fmt"
|
||||
"bytes"
|
||||
"io"
|
||||
"strconv"
|
||||
"errors"
|
||||
|
||||
"yunion.io/x/pkg/utils"
|
||||
"yunion.io/x/log"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudprovider"
|
||||
"yunion.io/x/onecloud/pkg/compute/models"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/qemutils"
|
||||
"yunion.io/x/onecloud/pkg/util/fileutils2"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnsupportedFormat = errors.New("unsupported format")
|
||||
)
|
||||
|
||||
type SQemuImage struct {
|
||||
Path string
|
||||
Password string
|
||||
Format TImageFormat
|
||||
SizeBytes int64
|
||||
ActualSizeBytes int64
|
||||
ClusterSize int
|
||||
BackFilePath string
|
||||
Compat string
|
||||
Encryption bool
|
||||
Subformat string
|
||||
}
|
||||
|
||||
func NewQemuImage(path string) (*SQemuImage, error) {
|
||||
return NewEncryptedQemuImage(path, "")
|
||||
}
|
||||
|
||||
func NewEncryptedQemuImage(path string, password string) (*SQemuImage, error) {
|
||||
qemuImg := SQemuImage{Path: path, Password: password}
|
||||
err := qemuImg.parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &qemuImg, nil
|
||||
}
|
||||
|
||||
func (img *SQemuImage) parse() error {
|
||||
if strings.HasPrefix(img.Path, "nbd") {
|
||||
// nbd TCP -> nbd:<server-ip>:<port>
|
||||
// nbd Unix Domain Sockets -> nbd:unix:<domain-socket-file>
|
||||
img.ActualSizeBytes = 0
|
||||
} else if strings.HasPrefix(img.Path, "iscsi") {
|
||||
// iSCSI LUN -> iscsi://<target-ip>[:<port>]/<target-iqn>/<lun>
|
||||
return cloudprovider.ErrNotImplemented
|
||||
} else if strings.HasPrefix(img.Path, "sheepdog") {
|
||||
// sheepdog -> sheepdog[+tcp|+unix]://[host:port]/vdiname[?socket=path][#snapid|#tag]
|
||||
return cloudprovider.ErrNotImplemented
|
||||
} else if strings.HasPrefix(img.Path, models.STORAGE_RBD) {
|
||||
img.ActualSizeBytes = 0
|
||||
} else {
|
||||
fileInfo, err := os.Stat(img.Path)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
img.ActualSizeBytes = fileInfo.Size()
|
||||
}
|
||||
}
|
||||
cmd := exec.Command(qemutils.GetQemuImg(), "info", img.Path)
|
||||
if len(img.Password) > 0 {
|
||||
cmd.Stdin = bytes.NewBuffer([]byte(img.Password))
|
||||
}
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Errorf("qemu-img info %s fail %s", img.Path, err)
|
||||
return fmt.Errorf("qemu-img info error %s", err)
|
||||
}
|
||||
for {
|
||||
line, err := out.ReadString('\n')
|
||||
if len(line) > 0 {
|
||||
line = strings.TrimSpace(line)
|
||||
switch {
|
||||
case strings.HasPrefix(line, "file format:"):
|
||||
img.Format = TImageFormat(line[strings.LastIndexByte(line, ' ')+1:])
|
||||
case strings.HasPrefix(line, "virtual size:"):
|
||||
sizeStr := line[strings.LastIndexByte(line, '(')+1 : strings.LastIndexByte(line, ' ')]
|
||||
size, err := strconv.ParseInt(sizeStr, 10, -1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid size str %s", sizeStr)
|
||||
}
|
||||
img.SizeBytes = size
|
||||
case strings.HasPrefix(line, "cluster_size:"):
|
||||
sizeStr := line[strings.LastIndexByte(line, ' ')+1:]
|
||||
size, err := strconv.ParseInt(sizeStr, 10, -1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid cluster size str %s", sizeStr)
|
||||
}
|
||||
img.ClusterSize = int(size)
|
||||
case strings.HasPrefix(line, "backing file:"):
|
||||
img.BackFilePath = line[strings.LastIndexByte(line, ' ')+1:]
|
||||
case strings.HasPrefix(line, "compat:"):
|
||||
img.Compat = line[strings.LastIndexByte(line, ' ')+1:]
|
||||
case strings.HasPrefix(line, "encrypted:"):
|
||||
if line[strings.LastIndexByte(line, ' ')+1:] == "yes" {
|
||||
img.Encryption = true
|
||||
}
|
||||
case strings.HasPrefix(line, "create type:"):
|
||||
img.Subformat = line[strings.LastIndexByte(line, ' ')+1:]
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else {
|
||||
log.Errorf("read output fail %s", err)
|
||||
return fmt.Errorf("read output fail %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if img.Format == RAW {
|
||||
// test if it is an ISO
|
||||
blkType := fileutils2.GetBlkidType(img.Path)
|
||||
if blkType == "iso9660" {
|
||||
img.Format = ISO
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (img *SQemuImage) IsValid() bool {
|
||||
return len(img.Format) > 0
|
||||
}
|
||||
|
||||
func (img *SQemuImage) IsChained() bool {
|
||||
return len(img.BackFilePath) > 0
|
||||
}
|
||||
|
||||
func (img *SQemuImage) doConvert(name string, format TImageFormat, options []string, compact bool, password string) error {
|
||||
if !img.IsValid() {
|
||||
return fmt.Errorf("self is not valid")
|
||||
}
|
||||
cmdline := []string{"convert"}
|
||||
if compact {
|
||||
cmdline = append(cmdline, "-c")
|
||||
}
|
||||
cmdline = append(cmdline, "-f", img.Format.String(), "-O", format.String())
|
||||
if len(password) > 0 {
|
||||
if options == nil {
|
||||
options = make([]string, 0)
|
||||
}
|
||||
options = append(options, "encryption=on")
|
||||
}
|
||||
if len(options) > 0 {
|
||||
cmdline = append(cmdline, "-o", strings.Join(options, ","))
|
||||
}
|
||||
cmdline = append(cmdline, img.Path, name)
|
||||
cmd := exec.Command(qemutils.GetQemuImg(), cmdline...)
|
||||
if len(img.Password) > 0 || len(password) > 0 {
|
||||
input := ""
|
||||
if len(img.Password) > 0 {
|
||||
input = fmt.Sprintf("%s\r", input, img.Password)
|
||||
}
|
||||
if len(password) > 0 {
|
||||
input = fmt.Sprintf("%s%s\r", input, password)
|
||||
}
|
||||
cmd.Stdin = bytes.NewBuffer([]byte(input))
|
||||
}
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Errorf("clone fail %s", err)
|
||||
os.Remove(name)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (img *SQemuImage) Clone(name string, format TImageFormat, compact bool) (*SQemuImage, error) {
|
||||
switch format {
|
||||
case QCOW2:
|
||||
return img.CloneQcow2(name, compact)
|
||||
case VMDK:
|
||||
return img.CloneVmdk(name, compact)
|
||||
case RAW:
|
||||
return img.CloneRaw(name)
|
||||
default:
|
||||
return nil, ErrUnsupportedFormat
|
||||
}
|
||||
}
|
||||
|
||||
func (img *SQemuImage) clone(name string, format TImageFormat, options []string, compact bool, password string) (*SQemuImage, error) {
|
||||
err := img.doConvert(name, format, options, compact, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewQemuImage(name)
|
||||
}
|
||||
|
||||
func (img *SQemuImage) convert(format TImageFormat, options []string, compact bool, password string) (error) {
|
||||
tmpPath := fmt.Sprintf("%s.%s", img.Path, utils.GenRequestId(36))
|
||||
err := img.doConvert(tmpPath, format, options, compact, password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := exec.Command("mv", "-f", tmpPath, img.Path)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
log.Errorf("convert move temp file error %s", err)
|
||||
os.Remove(tmpPath)
|
||||
return err
|
||||
}
|
||||
img.Password = password
|
||||
return img.parse()
|
||||
}
|
||||
|
||||
func (img *SQemuImage) Copy(name string) (*SQemuImage, error) {
|
||||
if !img.IsValid() {
|
||||
return nil, fmt.Errorf("self is not valid")
|
||||
}
|
||||
cmd := exec.Command("cp", "--sparse=always", img.Path, name)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Errorf("copy fail %s", err)
|
||||
os.Remove(name)
|
||||
return nil, err
|
||||
}
|
||||
return NewQemuImage(name)
|
||||
}
|
||||
|
||||
func (img *SQemuImage) Convert2Qcow2(compact bool) error {
|
||||
options := make([]string, 0)
|
||||
// if len(backPath) > 0 {
|
||||
// options = append(options, fmt.Sprintf("backing_file=%s", backPath))
|
||||
//} else
|
||||
if !compact {
|
||||
sparseOpts := qcow2SparseOptions()
|
||||
options = append(options, sparseOpts...)
|
||||
}
|
||||
return img.convert(QCOW2, options, compact, "")
|
||||
}
|
||||
|
||||
func (img *SQemuImage) Convert2Vmdk(compact bool) error {
|
||||
return img.convert(VMDK, vmdkOptions(compact), false, "")
|
||||
}
|
||||
|
||||
func (img *SQemuImage) Convert2Raw() error {
|
||||
return img.convert(RAW, nil, false, "")
|
||||
}
|
||||
|
||||
func (img *SQemuImage) IsRaw() bool {
|
||||
return img.Format == RAW
|
||||
}
|
||||
|
||||
func (img *SQemuImage) IsSparseQcow2() bool {
|
||||
return img.Format == QCOW2 && img.ClusterSize >= 1024*1024*2
|
||||
}
|
||||
|
||||
func (img *SQemuImage) IsSparseVmdk() bool {
|
||||
return img.Format == VMDK && img.Subformat != "streamOptimized"
|
||||
}
|
||||
|
||||
func (img *SQemuImage) IsSparse() bool {
|
||||
return img.IsRaw() || img.IsSparseQcow2() || img.IsSparseVmdk()
|
||||
}
|
||||
|
||||
func (img *SQemuImage) Expand() error {
|
||||
if img.IsSparse() {
|
||||
return nil
|
||||
}
|
||||
return img.Convert2Qcow2(false)
|
||||
}
|
||||
|
||||
func (img *SQemuImage) CloneQcow2(name string, compact bool) (*SQemuImage, error) {
|
||||
options := make([]string, 0)
|
||||
//if len(backPath) > 0 {
|
||||
// options = append(options, fmt.Sprintf("backing_file=%s", backPath))
|
||||
//} else
|
||||
if !compact {
|
||||
sparseOpts := qcow2SparseOptions()
|
||||
options = append(options, sparseOpts...)
|
||||
}
|
||||
return img.clone(name, QCOW2, options, compact, "")
|
||||
}
|
||||
|
||||
func vmdkOptions(compact bool) []string {
|
||||
if compact {
|
||||
return []string{"subformat=streamOptimized"}
|
||||
} else {
|
||||
return []string{"subformat=monolithicSparse"}
|
||||
}
|
||||
}
|
||||
|
||||
func (img *SQemuImage) CloneVmdk(name string, compact bool) (*SQemuImage, error) {
|
||||
return img.clone(name, VMDK, vmdkOptions(compact), compact, "")
|
||||
}
|
||||
|
||||
func (img *SQemuImage) CloneRaw(name string) (*SQemuImage, error) {
|
||||
return img.clone(name, RAW, nil, false, "")
|
||||
}
|
||||
|
||||
func (img *SQemuImage) create(sizeMB int, format TImageFormat, options []string) error {
|
||||
if img.IsValid() {
|
||||
return fmt.Errorf("The image is valid???")
|
||||
}
|
||||
args := []string{"create", "-f", format.String()}
|
||||
if len(options) > 0 {
|
||||
args = append(args, "-o", strings.Join(options, ","))
|
||||
}
|
||||
args = append(args, img.Path)
|
||||
if sizeMB > 0 {
|
||||
args = append(args, fmt.Sprintf("%dM", sizeMB))
|
||||
}
|
||||
cmd := exec.Command(qemutils.GetQemuImg(), args...)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Errorf("create error %s", err)
|
||||
return err
|
||||
}
|
||||
return img.parse()
|
||||
}
|
||||
|
||||
func (img *SQemuImage) CreateQcow2(sizeMB int, compact bool, backPath string) error {
|
||||
options := make([]string, 0)
|
||||
if len(backPath) > 0 {
|
||||
options = append(options, fmt.Sprintf("backing_file=%s", backPath))
|
||||
if !compact {
|
||||
options = append(options, "cluster_size=2M")
|
||||
}
|
||||
} else if !compact {
|
||||
sparseOpts := qcow2SparseOptions()
|
||||
options = append(options, sparseOpts...)
|
||||
}
|
||||
return img.create(sizeMB, QCOW2, options)
|
||||
}
|
||||
|
||||
func (img *SQemuImage) CreateVmdk(sizeMB int, compact bool) error {
|
||||
return img.create(sizeMB, VMDK, vmdkOptions(compact))
|
||||
}
|
||||
|
||||
func (img *SQemuImage) CreateRaw(sizeMB int) error {
|
||||
return img.create(sizeMB, RAW, nil)
|
||||
}
|
||||
|
||||
func (img *SQemuImage) GetSizeMB() int {
|
||||
return int(img.SizeBytes / 1024 / 1024)
|
||||
}
|
||||
|
||||
func (img *SQemuImage) GetActualSizeMB() int {
|
||||
return int(img.ActualSizeBytes / 1024 / 1024)
|
||||
}
|
||||
|
||||
func (img *SQemuImage) Resize(sizeMB int) error {
|
||||
if !img.IsValid() {
|
||||
return fmt.Errorf("self is not valid")
|
||||
}
|
||||
cmd := exec.Command(qemutils.GetQemuImg(), "resize", img.Path, fmt.Sprintf("%dM", sizeMB))
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Errorf("resize fail %s", err)
|
||||
return err
|
||||
}
|
||||
return img.parse()
|
||||
}
|
||||
|
||||
func (img *SQemuImage) Rebase(backPath string, force bool) error {
|
||||
if !img.IsValid() {
|
||||
return fmt.Errorf("self is not valid")
|
||||
}
|
||||
args := []string{"rebase"}
|
||||
if force {
|
||||
args = append(args, "-u")
|
||||
}
|
||||
args = append(args, "-b", backPath, img.Path)
|
||||
cmd := exec.Command(qemutils.GetQemuImg(), args...)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Errorf("rebase fail %s", err)
|
||||
return err
|
||||
}
|
||||
return img.parse()
|
||||
}
|
||||
|
||||
func (img *SQemuImage) Delete() error {
|
||||
if !img.IsValid() {
|
||||
return nil
|
||||
}
|
||||
err := os.Remove(img.Path)
|
||||
if err != nil {
|
||||
log.Errorf("delete fail %s", err)
|
||||
return err
|
||||
}
|
||||
img.Format = ""
|
||||
img.ActualSizeBytes = 0
|
||||
img.SizeBytes = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func (img *SQemuImage) Fallocate() error {
|
||||
if !img.IsValid() {
|
||||
return fmt.Errorf("self is not valid")
|
||||
}
|
||||
cmd := exec.Command("fallocate", "-l", fmt.Sprintf("%dm", img.GetSizeMB()), img.Path)
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func (img *SQemuImage) String() string {
|
||||
return fmt.Sprintf("Qemu %s %d(%d) %s", img.Format, img.GetSizeMB(), img.GetActualSizeMB(), img.Path)
|
||||
}
|
||||
150
pkg/cloudcommon/qemuimg/qemuimg_test.go
Normal file
150
pkg/cloudcommon/qemuimg/qemuimg_test.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package qemuimg
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGetQemuImgVersion(t *testing.T) {
|
||||
verStr := `qemu-img version 1.5.3, Copyright (c) 2004-2008 Fabrice Bellard`
|
||||
matches := qemuImgVersionRegexp.FindStringSubmatch(verStr)
|
||||
t.Logf("%s", matches[1])
|
||||
}
|
||||
|
||||
func TestQcow2(t *testing.T) {
|
||||
img, err := NewQemuImage("test")
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
err = img.CreateQcow2(1000, true, "")
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
t.Logf("%s %v", img, img.IsSparse())
|
||||
err = img.Delete()
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
err = img.CreateQcow2(1000, false, "")
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
t.Logf("%s %v", img, img.IsSparse())
|
||||
err = img.Convert2Qcow2(true)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
t.Logf("%s %v", img, img.IsSparse())
|
||||
err = img.Convert2Qcow2(false)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
t.Logf("%s %v", img, img.IsSparse())
|
||||
err = img.Resize(2048)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
t.Logf("%s %v", img, img.IsSparse())
|
||||
err = img.Convert2Qcow2(true)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
t.Logf("%s %v", img, img.IsSparse())
|
||||
err = img.Expand()
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
t.Logf("%s %v", img, img.IsSparse())
|
||||
err = img.Delete()
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
err = img.CreateQcow2(1000, true, "")
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
t.Logf("%s %v", img, img.IsSparse())
|
||||
img2, err := img.CloneQcow2("test2", true)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
t.Logf("%s %v", img2, img2.IsSparse())
|
||||
err = img2.Delete()
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
err = img.Convert2Qcow2(false)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
img4, err := NewQemuImage("test_top")
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
err = img4.CreateQcow2(0, true, img.Path)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
t.Logf("%s %v", img, img.IsSparse())
|
||||
t.Logf("%s %v", img4, img4.IsSparse())
|
||||
err = img.Convert2Qcow2(true)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
t.Logf("%s %v", img, img.IsSparse())
|
||||
t.Logf("%s %v", img4, img4.IsSparse())
|
||||
img4.Delete()
|
||||
img.Delete()
|
||||
}
|
||||
|
||||
func TestVmdk(t *testing.T) {
|
||||
img, err := NewQemuImage("test")
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
err = img.CreateVmdk(1024, true)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
t.Logf("%s %v", img, img.IsSparse())
|
||||
err = img.Delete()
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
err = img.CreateVmdk(1024, false)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
t.Logf("%s %v", img, img.IsSparse())
|
||||
err = img.Convert2Vmdk(true)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
t.Logf("%s %v", img, img.IsSparse())
|
||||
err = img.Convert2Vmdk(false)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
t.Logf("%s %v", img, img.IsSparse())
|
||||
img.Delete()
|
||||
}
|
||||
112
pkg/cloudcommon/qemutils/qemutils.go
Normal file
112
pkg/cloudcommon/qemutils/qemutils.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package qemutils
|
||||
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/version"
|
||||
)
|
||||
|
||||
const (
|
||||
USER_LOCAL_BIN = "/usr/local/bin"
|
||||
USER_BIN = "/usr/bin"
|
||||
)
|
||||
|
||||
func GetQemu(version string) string {
|
||||
return getQemuCmd("qemu-system-x86_64", version)
|
||||
}
|
||||
|
||||
func GetQemuNbd() string {
|
||||
return getQemuCmd("qemu-nbd", "")
|
||||
}
|
||||
|
||||
func GetQemuImg() string {
|
||||
return getQemuCmd("qemu-img", "")
|
||||
}
|
||||
|
||||
func getQemuCmd(cmd, version string) string {
|
||||
if len(version) > 0 {
|
||||
return getQemuCmdByVersion(cmd, version)
|
||||
} else {
|
||||
return getQemuDefaultCmd(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func getQemuCmdByVersion(cmd, version string) string {
|
||||
p := path.Join(fmt.Sprintf("/usr/local/qemu-%s/bin", version), cmd)
|
||||
if _, err := os.Stat(p); !os.IsNotExist(err) {
|
||||
return p
|
||||
}
|
||||
cmd = cmd + "_" + version
|
||||
p = path.Join(USER_LOCAL_BIN, cmd)
|
||||
if _, err := os.Stat(p); !os.IsNotExist(err) {
|
||||
return p
|
||||
}
|
||||
p = path.Join(USER_BIN, cmd)
|
||||
if _, err := os.Stat(p); !os.IsNotExist(err) {
|
||||
return p
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getQemuVersion(verString string) string {
|
||||
s := regexp.MustCompile(`qemu-(?P<ver>\d+(\.\d+)+)$`).FindString(verString)
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
return s[len("qemu-"):]
|
||||
}
|
||||
|
||||
func getCmdVersion(cmd string) string {
|
||||
s := regexp.MustCompile(`_(?P<ver>\d+(\.\d+)+)$`).FindString(cmd)
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
return s[1:]
|
||||
}
|
||||
|
||||
func getQemuDefaultCmd(cmd string) string {
|
||||
var qemus = make([]string, 0)
|
||||
if files, err := ioutil.ReadDir("/usr/local"); err == nil {
|
||||
for i := 0; i < len(files); i++ {
|
||||
if strings.HasPrefix(files[i].Name(), "qemu-") {
|
||||
qemus = append(qemus, files[i].Name())
|
||||
}
|
||||
}
|
||||
if len(qemus) > 0 {
|
||||
sort.Slice(qemus, func(i, j int) bool {
|
||||
return version.LT(getQemuVersion(qemus[i]),
|
||||
getQemuVersion(qemus[j]))
|
||||
})
|
||||
p := fmt.Sprintf("/usr/local/%s/bin/%s", qemus[len(qemus)-1], cmd)
|
||||
if _, err := os.Stat(p); !os.IsNotExist(err) {
|
||||
return p
|
||||
}
|
||||
}
|
||||
}
|
||||
cmds := make([]string, 0)
|
||||
if files, err := ioutil.ReadDir(USER_LOCAL_BIN); err == nil {
|
||||
for i := 0; i < len(files); i++ {
|
||||
if strings.HasPrefix(files[i].Name(), cmd) {
|
||||
cmds = append(cmds, files[i].Name())
|
||||
}
|
||||
}
|
||||
if len(cmds) > 0 {
|
||||
sort.Slice(cmds, func(i, j int) bool {
|
||||
return version.LT(getCmdVersion(cmds[i]),
|
||||
getCmdVersion(cmds[j]))
|
||||
})
|
||||
p := path.Join(USER_LOCAL_BIN, cmds[len(cmds)-1])
|
||||
if _, err := os.Stat(p); !os.IsNotExist(err) {
|
||||
return p
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
47
pkg/cloudcommon/version/version.go
Normal file
47
pkg/cloudcommon/version/version.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package version
|
||||
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func less(v1Str, v2Str string) (bool, bool) {
|
||||
v1 := strings.Split(v1Str, ".")
|
||||
v2 := strings.Split(v2Str, ".")
|
||||
var i = 0
|
||||
for ; i < len(v2); i++ {
|
||||
if i >= len(v1) {
|
||||
return true, false
|
||||
}
|
||||
v, _ := strconv.ParseInt(v2[i], 10, 0)
|
||||
compareV, _ := strconv.ParseInt(v1[i], 10, 0)
|
||||
if v < compareV {
|
||||
return false, false
|
||||
} else if compareV < v {
|
||||
return true, false
|
||||
}
|
||||
}
|
||||
if i < len(v1)-1 {
|
||||
return false, false
|
||||
}
|
||||
return true, true
|
||||
}
|
||||
|
||||
func LE(v1Str, v2Str string) bool {
|
||||
l, _ := less(v1Str, v2Str)
|
||||
return l
|
||||
}
|
||||
|
||||
func LT(v1Str, v2Str string) bool {
|
||||
l, e := less(v1Str, v2Str)
|
||||
return l && !e
|
||||
}
|
||||
|
||||
func GT(v1Str, v2Str string) bool {
|
||||
return LT(v2Str, v1Str)
|
||||
}
|
||||
|
||||
func GE(v1Str, v2Str string) bool {
|
||||
return LE(v2Str, v1Str)
|
||||
}
|
||||
@@ -5,12 +5,12 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/etcd"
|
||||
)
|
||||
|
||||
type CloudirOptions struct {
|
||||
type SCloudirOptions struct {
|
||||
etcd.SEtcdOptions
|
||||
|
||||
cloudcommon.Options
|
||||
}
|
||||
|
||||
var (
|
||||
Options CloudirOptions
|
||||
Options SCloudirOptions
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
package images
|
||||
|
||||
func AddHandler() {
|
||||
|
||||
func AddHandler()
|
||||
}
|
||||
|
||||
@@ -169,11 +169,14 @@ func (self *SBaremetalagent) GetZone() *SZone {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SBaremetalagent) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
func (self *SBaremetalagent) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
zone := self.GetZone()
|
||||
if zone != nil {
|
||||
extra.Set("zone", jsonutils.NewString(zone.GetName()))
|
||||
}
|
||||
return extra
|
||||
return extra, nil
|
||||
}
|
||||
|
||||
@@ -644,9 +644,12 @@ func (self *SCloudaccount) GetCustomizeColumns(ctx context.Context, userCred mcc
|
||||
return self.getMoreDetails(extra)
|
||||
}
|
||||
|
||||
func (self *SCloudaccount) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SEnabledStatusStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
return self.getMoreDetails(extra)
|
||||
func (self *SCloudaccount) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SEnabledStatusStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return self.getMoreDetails(extra), nil
|
||||
}
|
||||
|
||||
func migrateCloudprovider(cloudprovider *SCloudprovider) error {
|
||||
|
||||
@@ -589,9 +589,12 @@ func (self *SCloudprovider) GetCustomizeColumns(ctx context.Context, userCred mc
|
||||
return self.getMoreDetails(ctx, extra)
|
||||
}
|
||||
|
||||
func (self *SCloudprovider) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SEnabledStatusStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
return self.getMoreDetails(ctx, extra)
|
||||
func (self *SCloudprovider) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SEnabledStatusStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return self.getMoreDetails(ctx, extra), nil
|
||||
}
|
||||
|
||||
func (manager *SCloudproviderManager) InitializeData() error {
|
||||
|
||||
@@ -140,9 +140,12 @@ func (self *SCloudregion) GetCustomizeColumns(ctx context.Context, userCred mccl
|
||||
return self.getMoreDetails(extra)
|
||||
}
|
||||
|
||||
func (self *SCloudregion) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SEnabledStatusStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
return self.getMoreDetails(extra)
|
||||
func (self *SCloudregion) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SEnabledStatusStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return self.getMoreDetails(extra), nil
|
||||
}
|
||||
|
||||
func (manager *SCloudregionManager) GetRegionByProvider(provider string) ([]SCloudregion, error) {
|
||||
|
||||
@@ -372,7 +372,7 @@ func (manager *SDiskManager) ValidateCreateData(ctx context.Context, userCred mc
|
||||
}
|
||||
pendingUsage := SQuota{Storage: diskConfig.SizeMb}
|
||||
if err := QuotaManager.CheckSetPendingQuota(ctx, userCred, userCred.GetProjectId(), &pendingUsage); err != nil {
|
||||
return nil, err
|
||||
return nil, httperrors.NewOutOfQuotaError("%s", err)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
@@ -719,10 +719,13 @@ func (self *SDisk) PrepareSaveImage(ctx context.Context, userCred mcclient.Token
|
||||
} else if imageList.Total > 0 {
|
||||
return "", httperrors.NewConflictError("Duplicate image name %s", name)
|
||||
}
|
||||
quota := SQuota{Image: 1}
|
||||
if _, err := QuotaManager.CheckQuota(ctx, userCred, userCred.GetProjectId(), "a); err != nil {
|
||||
/*
|
||||
no need to check quota anymore
|
||||
session := auth.GetSession(userCred, options.Options.Region, "v2")
|
||||
quota := image_models.SQuota{Image: 1}
|
||||
if _, err := modules.ImageQuotas.DoQuotaCheck(session, jsonutils.Marshal("a)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}*/
|
||||
data.Add(jsonutils.NewInt(int64(self.DiskSize)), "virtual_size")
|
||||
if result, err := modules.Images.Create(s, data); err != nil {
|
||||
return "", err
|
||||
@@ -1354,9 +1357,12 @@ func (self *SDisk) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils.JSONDict
|
||||
return extra
|
||||
}
|
||||
|
||||
func (self *SDisk) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SSharableVirtualResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
return self.getMoreDetails(extra)
|
||||
func (self *SDisk) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SSharableVirtualResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return self.getMoreDetails(extra), nil
|
||||
}
|
||||
|
||||
func (self *SDisk) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
|
||||
@@ -131,9 +131,12 @@ func (self *SDynamicschedtag) GetCustomizeColumns(ctx context.Context, userCred
|
||||
return self.getMoreColumns(extra)
|
||||
}
|
||||
|
||||
func (self *SDynamicschedtag) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
return self.getMoreColumns(extra)
|
||||
func (self *SDynamicschedtag) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return self.getMoreColumns(extra), nil
|
||||
}
|
||||
|
||||
func (manager *SDynamicschedtagManager) getAllEnabledDynamicSchedtags() []SDynamicschedtag {
|
||||
|
||||
@@ -704,9 +704,12 @@ func (self *SElasticip) StartEipSyncstatusTask(ctx context.Context, userCred mcc
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SElasticip) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SVirtualResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
return self.getMoreDetails(extra)
|
||||
func (self *SElasticip) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SVirtualResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return self.getMoreDetails(extra), nil
|
||||
}
|
||||
|
||||
func (self *SElasticip) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
|
||||
@@ -49,9 +49,12 @@ func (self *SGroupguest) GetCustomizeColumns(ctx context.Context, userCred mccli
|
||||
return db.JointModelExtra(self, extra)
|
||||
}
|
||||
|
||||
func (self *SGroupguest) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SGroupJointsBase.GetExtraDetails(ctx, userCred, query)
|
||||
return db.JointModelExtra(self, extra)
|
||||
func (self *SGroupguest) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SGroupJointsBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db.JointModelExtra(self, extra), nil
|
||||
}
|
||||
|
||||
func (self *SGroupguest) GetGuest() *SGuest {
|
||||
|
||||
@@ -48,9 +48,12 @@ func (self *SGroupnetwork) GetCustomizeColumns(ctx context.Context, userCred mcc
|
||||
return db.JointModelExtra(self, extra)
|
||||
}
|
||||
|
||||
func (self *SGroupnetwork) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SGroupJointsBase.GetExtraDetails(ctx, userCred, query)
|
||||
return db.JointModelExtra(self, extra)
|
||||
func (self *SGroupnetwork) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SGroupJointsBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db.JointModelExtra(self, extra), nil
|
||||
}
|
||||
|
||||
func (self *SGroupnetwork) getNetwork() *SNetwork {
|
||||
|
||||
@@ -1069,7 +1069,7 @@ func (self *SGuest) PerformCreatedisk(ctx context.Context, userCred mcclient.Tok
|
||||
err := QuotaManager.CheckSetPendingQuota(ctx, userCred, self.ProjectId, pendingUsage)
|
||||
if err != nil {
|
||||
logclient.AddActionLog(self, logclient.ACT_CREATE, err.Error(), userCred, false)
|
||||
return nil, httperrors.NewBadRequestError(err.Error())
|
||||
return nil, httperrors.NewOutOfQuotaError(err.Error())
|
||||
}
|
||||
|
||||
lockman.LockObject(ctx, host)
|
||||
@@ -1500,7 +1500,7 @@ func (self *SGuest) PerformChangeConfig(ctx context.Context, userCred mcclient.T
|
||||
if !pendingUsage.IsEmpty() {
|
||||
err := QuotaManager.CheckSetPendingQuota(ctx, userCred, userCred.GetProjectId(), pendingUsage)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewBadRequestError("Check set pending quota error %s", err)
|
||||
return nil, httperrors.NewOutOfQuotaError("Check set pending quota error %s", err)
|
||||
}
|
||||
}
|
||||
if newDisks.Length() > 0 {
|
||||
@@ -1642,7 +1642,7 @@ func (self *SGuest) PerformDiskSnapshot(ctx context.Context, userCred mcclient.T
|
||||
pendingUsage := &SQuota{Snapshot: 1}
|
||||
err = QuotaManager.CheckSetPendingQuota(ctx, userCred, self.ProjectId, pendingUsage)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewBadRequestError("Check set pending quota error %s", err)
|
||||
return nil, httperrors.NewOutOfQuotaError("Check set pending quota error %s", err)
|
||||
}
|
||||
snapshot, err := SnapshotManager.CreateSnapshot(ctx, userCred, MANUAL, diskId, self.Id, "", name)
|
||||
QuotaManager.CancelPendingUsage(ctx, userCred, self.ProjectId, nil, pendingUsage)
|
||||
@@ -2078,7 +2078,7 @@ func (self *SGuest) PerformCreateBackup(ctx context.Context, userCred mcclient.T
|
||||
req := self.getGuestBackupResourceRequirements(ctx, userCred)
|
||||
err := QuotaManager.CheckSetPendingQuota(ctx, userCred, self.GetOwnerProjectId(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, httperrors.NewOutOfQuotaError(err.Error())
|
||||
}
|
||||
|
||||
params := data.(*jsonutils.JSONDict)
|
||||
|
||||
@@ -96,10 +96,13 @@ func (self *SGuestdisk) GetCustomizeColumns(ctx context.Context, userCred mcclie
|
||||
return self.getExtraInfo(extra)
|
||||
}
|
||||
|
||||
func (self *SGuestdisk) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SGuestJointsBase.GetExtraDetails(ctx, userCred, query)
|
||||
func (self *SGuestdisk) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SGuestJointsBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extra = db.JointModelExtra(self, extra)
|
||||
return self.getExtraInfo(extra)
|
||||
return self.getExtraInfo(extra), nil
|
||||
}
|
||||
|
||||
func (self *SGuestdisk) DoSave(driver string, cache string, mountpoint string) error {
|
||||
|
||||
@@ -73,9 +73,12 @@ func (self *SGuestnetwork) GetCustomizeColumns(ctx context.Context, userCred mcc
|
||||
return db.JointModelExtra(self, extra)
|
||||
}
|
||||
|
||||
func (self *SGuestnetwork) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SGuestJointsBase.GetExtraDetails(ctx, userCred, query)
|
||||
return db.JointModelExtra(self, extra)
|
||||
func (self *SGuestnetwork) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SGuestJointsBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db.JointModelExtra(self, extra), nil
|
||||
}
|
||||
|
||||
func (manager *SGuestnetworkManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
|
||||
@@ -1208,8 +1208,12 @@ func (self *SGuest) moreExtraInfo(extra *jsonutils.JSONDict) *jsonutils.JSONDict
|
||||
return extra
|
||||
}
|
||||
|
||||
func (self *SGuest) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SVirtualResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
func (self *SGuest) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SVirtualResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
extra.Add(jsonutils.NewString(self.getNetworksDetails()), "networks")
|
||||
extra.Add(jsonutils.NewString(self.getDisksDetails()), "disks")
|
||||
extra.Add(self.getDisksInfoDetails(), "disks_info")
|
||||
@@ -1261,7 +1265,7 @@ func (self *SGuest) GetExtraDetails(ctx context.Context, userCred mcclient.Token
|
||||
extra.Add(jsonutils.JSONFalse, "is_prepaid_recycle")
|
||||
}
|
||||
|
||||
return self.moreExtraInfo(extra)
|
||||
return self.moreExtraInfo(extra), nil
|
||||
}
|
||||
|
||||
func (manager *SGuestManager) ListItemExportKeys(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
|
||||
|
||||
@@ -57,9 +57,12 @@ func (bn *SHostnetwork) GetCustomizeColumns(ctx context.Context, userCred mcclie
|
||||
return extra
|
||||
}
|
||||
|
||||
func (bn *SHostnetwork) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := bn.SHostJointsBase.GetExtraDetails(ctx, userCred, query)
|
||||
return db.JointModelExtra(bn, extra)
|
||||
func (bn *SHostnetwork) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := bn.SHostJointsBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db.JointModelExtra(bn, extra), nil
|
||||
}
|
||||
|
||||
func (bn *SHostnetwork) GetHost() *SHost {
|
||||
|
||||
@@ -2125,9 +2125,12 @@ func (self *SHost) GetCustomizeColumns(ctx context.Context, userCred mcclient.To
|
||||
return self.getMoreDetails(ctx, extra)
|
||||
}
|
||||
|
||||
func (self *SHost) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SEnabledStatusStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
return self.getMoreDetails(ctx, extra)
|
||||
func (self *SHost) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SEnabledStatusStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return self.getMoreDetails(ctx, extra), nil
|
||||
}
|
||||
|
||||
func (self *SHost) AllowGetDetailsVnc(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
|
||||
@@ -2195,8 +2198,8 @@ func (manager *SHostManager) GetHostsByManagerAndRegion(managerId string, region
|
||||
return nil
|
||||
}*/
|
||||
|
||||
func (self *SHost) Request(userCred mcclient.TokenCredential, method string, url string, headers http.Header, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
s := auth.GetSession(nil, userCred, "", "")
|
||||
func (self *SHost) Request(ctx context.Context, userCred mcclient.TokenCredential, method httputils.THttpMethod, url string, headers http.Header, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
s := auth.GetSession(ctx, userCred, "", "")
|
||||
_, ret, err := s.JSONRequest(self.ManagerUri, "", method, url, headers, body)
|
||||
return ret, err
|
||||
}
|
||||
@@ -2605,7 +2608,7 @@ func (self *SHost) StartBaremetalUnmaintenanceTask(ctx context.Context, userCred
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SHost) BaremetalSyncRequest(ctx context.Context, method, url string, headers http.Header, body *jsonutils.JSONDict) (jsonutils.JSONObject, error) {
|
||||
func (self *SHost) BaremetalSyncRequest(ctx context.Context, method httputils.THttpMethod, url string, headers http.Header, body *jsonutils.JSONDict) (jsonutils.JSONObject, error) {
|
||||
serviceUrl, err := auth.GetServiceURL("baremetal", options.Options.Region, self.GetZone().GetName(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -49,9 +49,12 @@ func (self *SHostschedtag) GetCustomizeColumns(ctx context.Context, userCred mcc
|
||||
return db.JointModelExtra(self, extra)
|
||||
}
|
||||
|
||||
func (self *SHostschedtag) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SHostJointsBase.GetExtraDetails(ctx, userCred, query)
|
||||
return db.JointModelExtra(self, extra)
|
||||
func (self *SHostschedtag) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SHostJointsBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db.JointModelExtra(self, extra), nil
|
||||
}
|
||||
|
||||
func (self *SHostschedtag) getHost() *SHost {
|
||||
|
||||
@@ -63,10 +63,13 @@ func (self *SHoststorage) GetCustomizeColumns(ctx context.Context, userCred mccl
|
||||
return self.getExtraDetails(extra)
|
||||
}
|
||||
|
||||
func (self *SHoststorage) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SHostJointsBase.GetExtraDetails(ctx, userCred, query)
|
||||
func (self *SHoststorage) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SHostJointsBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extra = db.JointModelExtra(self, extra)
|
||||
return self.getExtraDetails(extra)
|
||||
return self.getExtraDetails(extra), nil
|
||||
}
|
||||
|
||||
func (self *SHoststorage) GetHost() *SHost {
|
||||
|
||||
@@ -56,10 +56,13 @@ func (self *SHostwire) GetCustomizeColumns(ctx context.Context, userCred mcclien
|
||||
return self.getExtraDetails(extra)
|
||||
}
|
||||
|
||||
func (self *SHostwire) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SHostJointsBase.GetExtraDetails(ctx, userCred, query)
|
||||
func (self *SHostwire) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SHostJointsBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extra = db.JointModelExtra(self, extra)
|
||||
return self.getExtraDetails(extra)
|
||||
return self.getExtraDetails(extra), nil
|
||||
}
|
||||
|
||||
func (hw *SHostwire) GetWire() *SWire {
|
||||
|
||||
@@ -498,10 +498,13 @@ func (self *SIsolatedDevice) getMoreDetails(extra *jsonutils.JSONDict) *jsonutil
|
||||
return extra
|
||||
}
|
||||
|
||||
func (self *SIsolatedDevice) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
func (self *SIsolatedDevice) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extra = self.getMoreDetails(extra)
|
||||
return extra
|
||||
return extra, nil
|
||||
}
|
||||
|
||||
func (self *SIsolatedDevice) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
|
||||
@@ -81,8 +81,11 @@ func (self *SKeypair) GetCustomizeColumns(ctx context.Context, userCred mcclient
|
||||
return extra
|
||||
}
|
||||
|
||||
func (self *SKeypair) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
func (self *SKeypair) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extra.Add(jsonutils.NewInt(int64(len(self.PrivateKey))), "private_key_len")
|
||||
extra.Add(jsonutils.NewInt(int64(self.GetLinkedGuestsCount())), "linked_guest_count")
|
||||
if db.IsAdminAllowGet(userCred, self) {
|
||||
@@ -92,7 +95,7 @@ func (self *SKeypair) GetExtraDetails(ctx context.Context, userCred mcclient.Tok
|
||||
extra.Add(jsonutils.NewString(uc.Name), "owner_name")
|
||||
}
|
||||
}
|
||||
return extra
|
||||
return extra, nil
|
||||
}
|
||||
|
||||
func (manager *SKeypairManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
|
||||
@@ -118,9 +118,9 @@ func (lbbg *SLoadbalancerBackendGroup) GetCustomizeColumns(ctx context.Context,
|
||||
return extra
|
||||
}
|
||||
|
||||
func (lbbg *SLoadbalancerBackendGroup) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
func (lbbg *SLoadbalancerBackendGroup) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra := lbbg.GetCustomizeColumns(ctx, userCred, query)
|
||||
return extra
|
||||
return extra, nil
|
||||
}
|
||||
|
||||
func (lbbg *SLoadbalancerBackendGroup) PreDelete(ctx context.Context, userCred mcclient.TokenCredential) {
|
||||
|
||||
@@ -171,9 +171,9 @@ func (lbr *SLoadbalancerListenerRule) GetCustomizeColumns(ctx context.Context, u
|
||||
return extra
|
||||
}
|
||||
|
||||
func (lbr *SLoadbalancerListenerRule) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
func (lbr *SLoadbalancerListenerRule) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra := lbr.GetCustomizeColumns(ctx, userCred, query)
|
||||
return extra
|
||||
return extra, nil
|
||||
}
|
||||
|
||||
func (lbr *SLoadbalancerListenerRule) PreDelete(ctx context.Context, userCred mcclient.TokenCredential) {
|
||||
|
||||
@@ -389,9 +389,9 @@ func (lblis *SLoadbalancerListener) GetCustomizeColumns(ctx context.Context, use
|
||||
return extra
|
||||
}
|
||||
|
||||
func (lblis *SLoadbalancerListener) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
func (lblis *SLoadbalancerListener) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra := lblis.GetCustomizeColumns(ctx, userCred, query)
|
||||
return extra
|
||||
return extra, nil
|
||||
}
|
||||
|
||||
func (lblis *SLoadbalancerListener) PreDelete(ctx context.Context, userCred mcclient.TokenCredential) {
|
||||
|
||||
@@ -194,9 +194,9 @@ func (lb *SLoadbalancer) GetCustomizeColumns(ctx context.Context, userCred mccli
|
||||
return extra
|
||||
}
|
||||
|
||||
func (lb *SLoadbalancer) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
func (lb *SLoadbalancer) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra := lb.GetCustomizeColumns(ctx, userCred, query)
|
||||
return extra
|
||||
return extra, nil
|
||||
}
|
||||
|
||||
func (lb *SLoadbalancer) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
|
||||
|
||||
@@ -869,10 +869,13 @@ func (self *SNetwork) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils.JSOND
|
||||
return extra
|
||||
}
|
||||
|
||||
func (self *SNetwork) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SSharableVirtualResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
func (self *SNetwork) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SSharableVirtualResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extra = self.getMoreDetails(extra)
|
||||
return extra
|
||||
return extra, nil
|
||||
}
|
||||
|
||||
func (self *SNetwork) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
|
||||
@@ -8,8 +8,6 @@ import (
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/quotas"
|
||||
"yunion.io/x/onecloud/pkg/compute/options"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
"yunion.io/x/pkg/tristate"
|
||||
)
|
||||
|
||||
@@ -23,16 +21,16 @@ func init() {
|
||||
}
|
||||
|
||||
var (
|
||||
ErrOutOfCPU = errors.New("out of CPU quota")
|
||||
ErrOutOfMemory = errors.New("out of memory quota")
|
||||
ErrOutOfStorage = errors.New("out of storage quota")
|
||||
ErrOutOfPort = errors.New("out of internal port quota")
|
||||
ErrOutOfEip = errors.New("out of eip quota")
|
||||
ErrOutOfEport = errors.New("out of external port quota")
|
||||
ErrOutOfBw = errors.New("out of internal bandwidth quota")
|
||||
ErrOutOfEbw = errors.New("out of external bandwidth quota")
|
||||
ErrOutOfKeypair = errors.New("out of keypair quota")
|
||||
ErrOutOfImage = errors.New("out of image quota")
|
||||
ErrOutOfCPU = errors.New("out of CPU quota")
|
||||
ErrOutOfMemory = errors.New("out of memory quota")
|
||||
ErrOutOfStorage = errors.New("out of storage quota")
|
||||
ErrOutOfPort = errors.New("out of internal port quota")
|
||||
ErrOutOfEip = errors.New("out of eip quota")
|
||||
ErrOutOfEport = errors.New("out of external port quota")
|
||||
ErrOutOfBw = errors.New("out of internal bandwidth quota")
|
||||
ErrOutOfEbw = errors.New("out of external bandwidth quota")
|
||||
ErrOutOfKeypair = errors.New("out of keypair quota")
|
||||
// ErrOutOfImage = errors.New("out of image quota")
|
||||
ErrOutOfGroup = errors.New("out of group quota")
|
||||
ErrOutOfSecgroup = errors.New("out of secgroup quota")
|
||||
ErrOutOfIsolatedDevice = errors.New("out of isolated device quota")
|
||||
@@ -40,16 +38,16 @@ var (
|
||||
)
|
||||
|
||||
type SQuota struct {
|
||||
Cpu int
|
||||
Memory int
|
||||
Storage int
|
||||
Port int
|
||||
Eip int
|
||||
Eport int
|
||||
Bw int
|
||||
Ebw int
|
||||
Keypair int
|
||||
Image int
|
||||
Cpu int
|
||||
Memory int
|
||||
Storage int
|
||||
Port int
|
||||
Eip int
|
||||
Eport int
|
||||
Bw int
|
||||
Ebw int
|
||||
Keypair int
|
||||
// Image int
|
||||
Group int
|
||||
Secgroup int
|
||||
IsolatedDevice int
|
||||
@@ -66,7 +64,6 @@ func (self *SQuota) FetchSystemQuota() {
|
||||
self.Bw = options.Options.DefaultBwQuota
|
||||
self.Ebw = options.Options.DefaultEbwQuota
|
||||
self.Keypair = options.Options.DefaultKeypairQuota
|
||||
self.Image = options.Options.DefaultImageQuota
|
||||
self.Group = options.Options.DefaultGroupQuota
|
||||
self.Secgroup = options.Options.DefaultSecgroupQuota
|
||||
self.IsolatedDevice = options.Options.DefaultIsolatedDeviceQuota
|
||||
@@ -92,8 +89,6 @@ func (self *SQuota) FetchUsage(ctx context.Context, projectId string) error {
|
||||
self.Bw = net.InternalBandwidth
|
||||
self.Ebw = net.ExternalBandwidth
|
||||
self.Keypair = 0 // keypair
|
||||
s := auth.GetAdminSession(ctx, options.Options.Region, "")
|
||||
self.Image, _ = modules.Images.GetPrivateImageCount(s, projectId, true)
|
||||
self.Group = 0
|
||||
self.Secgroup = totalSecurityGroupCount(projectId)
|
||||
self.IsolatedDevice = guest.TotalIsolatedCount
|
||||
@@ -129,9 +124,6 @@ func (self *SQuota) IsEmpty() bool {
|
||||
if self.Keypair > 0 {
|
||||
return false
|
||||
}
|
||||
if self.Image > 0 {
|
||||
return false
|
||||
}
|
||||
if self.Group > 0 {
|
||||
return false
|
||||
}
|
||||
@@ -158,7 +150,6 @@ func (self *SQuota) Add(quota quotas.IQuota) {
|
||||
self.Bw = self.Bw + squota.Bw
|
||||
self.Ebw = self.Ebw + squota.Ebw
|
||||
self.Keypair = self.Keypair + squota.Keypair
|
||||
self.Image = self.Image + squota.Image
|
||||
self.Group = self.Group + squota.Group
|
||||
self.Secgroup = self.Secgroup + squota.Secgroup
|
||||
self.IsolatedDevice = self.IsolatedDevice + squota.IsolatedDevice
|
||||
@@ -166,11 +157,7 @@ func (self *SQuota) Add(quota quotas.IQuota) {
|
||||
}
|
||||
|
||||
func nonNegative(val int) int {
|
||||
if val < 0 {
|
||||
return 0
|
||||
} else {
|
||||
return val
|
||||
}
|
||||
return quotas.NonNegative(val)
|
||||
}
|
||||
|
||||
func (self *SQuota) Sub(quota quotas.IQuota) {
|
||||
@@ -184,7 +171,6 @@ func (self *SQuota) Sub(quota quotas.IQuota) {
|
||||
self.Bw = nonNegative(self.Bw - squota.Bw)
|
||||
self.Ebw = nonNegative(self.Ebw - squota.Ebw)
|
||||
self.Keypair = nonNegative(self.Keypair - squota.Keypair)
|
||||
self.Image = nonNegative(self.Image - squota.Image)
|
||||
self.Group = nonNegative(self.Group - squota.Group)
|
||||
self.Secgroup = nonNegative(self.Secgroup - squota.Secgroup)
|
||||
self.IsolatedDevice = nonNegative(self.IsolatedDevice - squota.IsolatedDevice)
|
||||
@@ -220,9 +206,6 @@ func (self *SQuota) Update(quota quotas.IQuota) {
|
||||
if squota.Keypair > 0 {
|
||||
self.Keypair = squota.Keypair
|
||||
}
|
||||
if squota.Image > 0 {
|
||||
self.Image = squota.Image
|
||||
}
|
||||
if squota.Group > 0 {
|
||||
self.Group = squota.Group
|
||||
}
|
||||
@@ -267,9 +250,6 @@ func (self *SQuota) Exceed(request quotas.IQuota, quota quotas.IQuota) error {
|
||||
if sreq.Keypair > 0 && self.Keypair > squota.Keypair {
|
||||
return ErrOutOfKeypair
|
||||
}
|
||||
if sreq.Image > 0 && self.Image > squota.Image {
|
||||
return ErrOutOfImage
|
||||
}
|
||||
if sreq.Group > 0 && self.Group > squota.Group {
|
||||
return ErrOutOfGroup
|
||||
}
|
||||
@@ -322,9 +302,6 @@ func (self *SQuota) ToJSON(prefix string) jsonutils.JSONObject {
|
||||
if self.Keypair > 0 {
|
||||
ret.Add(jsonutils.NewInt(int64(self.Keypair)), keyName(prefix, "keypair"))
|
||||
}
|
||||
if self.Image > 0 {
|
||||
ret.Add(jsonutils.NewInt(int64(self.Image)), keyName(prefix, "image"))
|
||||
}
|
||||
if self.Group > 0 {
|
||||
ret.Add(jsonutils.NewInt(int64(self.Group)), keyName(prefix, "group"))
|
||||
}
|
||||
|
||||
@@ -327,10 +327,10 @@ func (rt *SRouteTable) GetCustomizeColumns(ctx context.Context, userCred mcclien
|
||||
return extra
|
||||
}
|
||||
|
||||
func (rt *SRouteTable) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
func (rt *SRouteTable) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra := rt.GetCustomizeColumns(ctx, userCred, query)
|
||||
extra = rt.getMoreDetails(extra)
|
||||
return extra
|
||||
return extra, nil
|
||||
}
|
||||
|
||||
func (man *SRouteTableManager) SyncRouteTables(ctx context.Context, userCred mcclient.TokenCredential, vpc *SVpc, cloudRouteTables []cloudprovider.ICloudRouteTable) ([]SRouteTable, []cloudprovider.ICloudRouteTable, compare.SyncResult) {
|
||||
|
||||
@@ -120,9 +120,12 @@ func (self *SSchedpolicy) GetCustomizeColumns(ctx context.Context, userCred mccl
|
||||
return self.getMoreColumns(extra)
|
||||
}
|
||||
|
||||
func (self *SSchedpolicy) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
return self.getMoreColumns(extra)
|
||||
func (self *SSchedpolicy) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return self.getMoreColumns(extra), nil
|
||||
}
|
||||
|
||||
func (manager *SSchedpolicyManager) getAllEnabledPolicies() []SSchedpolicy {
|
||||
|
||||
@@ -193,9 +193,12 @@ func (self *SSchedtag) GetCustomizeColumns(ctx context.Context, userCred mcclien
|
||||
return self.getMoreColumns(extra)
|
||||
}
|
||||
|
||||
func (self *SSchedtag) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
return self.getMoreColumns(extra)
|
||||
func (self *SSchedtag) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return self.getMoreColumns(extra), nil
|
||||
}
|
||||
|
||||
/*func (self *SSchedtag) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
|
||||
|
||||
@@ -84,15 +84,18 @@ func (self *SSecurityGroup) getDesc() jsonutils.JSONObject {
|
||||
return desc
|
||||
}
|
||||
|
||||
func (self *SSecurityGroup) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SSharableVirtualResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
func (self *SSecurityGroup) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SSharableVirtualResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extra.Add(jsonutils.NewInt(int64(len(self.GetGuests()))), "guest_cnt")
|
||||
extra.Add(jsonutils.NewString(self.getSecurityRuleString("")), "rules")
|
||||
return extra
|
||||
return extra, nil
|
||||
}
|
||||
|
||||
func (self *SSecurityGroup) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SSharableVirtualResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
extra := self.SSharableVirtualResourceBase.GetCustomizeColumns(ctx, userCred, query)
|
||||
extra.Add(jsonutils.NewInt(int64(len(self.GetGuests()))), "guest_cnt")
|
||||
extra.Add(jsonutils.NewTimeString(self.CreatedAt), "created_at")
|
||||
extra.Add(jsonutils.NewString(self.Description), "description")
|
||||
|
||||
@@ -162,9 +162,12 @@ func (self *SSnapshot) GetCustomizeColumns(ctx context.Context, userCred mcclien
|
||||
return self.getMoreDetails(extra)
|
||||
}
|
||||
|
||||
func (self *SSnapshot) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SVirtualResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
return self.getMoreDetails(extra)
|
||||
func (self *SSnapshot) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SVirtualResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return self.getMoreDetails(extra), nil
|
||||
}
|
||||
|
||||
func (self *SSnapshot) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils.JSONDict {
|
||||
|
||||
@@ -139,10 +139,13 @@ func (self *SStoragecachedimage) GetCustomizeColumns(ctx context.Context, userCr
|
||||
return self.getExtraDetails(extra)
|
||||
}
|
||||
|
||||
func (self *SStoragecachedimage) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SJointResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
func (self *SStoragecachedimage) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SJointResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extra = db.JointModelExtra(self, extra)
|
||||
return self.getExtraDetails(extra)
|
||||
return self.getExtraDetails(extra), nil
|
||||
}
|
||||
|
||||
func (manager *SStoragecachedimageManager) AllowListDescendent(ctx context.Context, userCred mcclient.TokenCredential, model db.IStandaloneModel, query jsonutils.JSONObject) bool {
|
||||
|
||||
@@ -185,10 +185,13 @@ func (self *SStoragecache) syncWithCloudStoragecache(cloudCache cloudprovider.IC
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *SStoragecache) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
func (self *SStoragecache) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extra = self.getMoreDetails(extra)
|
||||
return extra
|
||||
return extra, nil
|
||||
}
|
||||
|
||||
func (self *SStoragecache) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
|
||||
@@ -459,9 +459,12 @@ func (self *SStorage) GetCustomizeColumns(ctx context.Context, userCred mcclient
|
||||
return self.getMoreDetails(extra)
|
||||
}
|
||||
|
||||
func (self *SStorage) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
return self.getMoreDetails(extra)
|
||||
func (self *SStorage) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return self.getMoreDetails(extra), nil
|
||||
}
|
||||
|
||||
func (self *SStorage) GetUsedCapacity(isReady tristate.TriState) int {
|
||||
|
||||
@@ -193,9 +193,12 @@ func (self *SVpc) GetCustomizeColumns(ctx context.Context, userCred mcclient.Tok
|
||||
return self.getMoreDetails(extra)
|
||||
}
|
||||
|
||||
func (self *SVpc) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SEnabledStatusStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
return self.getMoreDetails(extra)
|
||||
func (self *SVpc) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SEnabledStatusStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return self.getMoreDetails(extra), nil
|
||||
}
|
||||
|
||||
func (manager *SVpcManager) getVpcsByRegion(region *SCloudregion, provider *SCloudprovider) ([]SVpc, error) {
|
||||
|
||||
@@ -692,9 +692,12 @@ func (self *SWire) GetCustomizeColumns(ctx context.Context, userCred mcclient.To
|
||||
return self.getMoreDetails(extra)
|
||||
}
|
||||
|
||||
func (self *SWire) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
return self.getMoreDetails(extra)
|
||||
func (self *SWire) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return self.getMoreDetails(extra), nil
|
||||
}
|
||||
|
||||
func (self *SWire) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils.JSONDict {
|
||||
|
||||
@@ -314,9 +314,12 @@ func (zone *SZone) GetCustomizeColumns(ctx context.Context, userCred mcclient.To
|
||||
return zoneExtra(zone, extra)
|
||||
}
|
||||
|
||||
func (zone *SZone) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := zone.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
return zoneExtra(zone, extra)
|
||||
func (zone *SZone) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := zone.SStandaloneResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return zoneExtra(zone, extra), nil
|
||||
}
|
||||
|
||||
func (zone *SZone) GetCloudRegionId() string {
|
||||
|
||||
@@ -2,6 +2,7 @@ package options
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/pending_delete"
|
||||
)
|
||||
|
||||
type ComputeOptions struct {
|
||||
@@ -21,10 +22,7 @@ type ComputeOptions struct {
|
||||
|
||||
DefaultDiskSize int `default:"30720" help:"Default disk size in MB if not specified, default to 30GiB"`
|
||||
|
||||
EnablePendingDelete bool `default:"true" help:"Turn on/off pending delete VM and disk, default is on"`
|
||||
PendingDeleteCheckSeconds int `default:"3600" help:"How long to wait to scan pending delete VM or disks, default is 1 hour"`
|
||||
PendingDeleteExpireSeconds int `default:"259200" help:"How long a pending delete VM/disks cleaned automatically, default 3 days"`
|
||||
PendingDeleteMaxCleanBatchSize int `default:"50" help:"How many pending delete servers can be clean in a batch"`
|
||||
pending_delete.SPendingDeleteOptions
|
||||
|
||||
PrepaidExpireCheckSeconds int `default:"600" help:"How long to wait to scan expired prepaid VM or disks, default is 10 minutes"`
|
||||
ExpiredPrepaidMaxCleanBatchSize int `default:"50" help:"How many expired prepaid servers can be deleted in a batch"`
|
||||
@@ -45,7 +43,6 @@ type ComputeOptions struct {
|
||||
DefaultBwQuota int `default:"2000000" help:"Common network port bandwidth in mbps quota per tenant, default 200*10Gbps"`
|
||||
DefaultEbwQuota int `default:"4000" help:"Common exit network port bandwidth quota per tenant, default 4Gbps"`
|
||||
DefaultKeypairQuota int `default:"50" help:"Common keypair quota per tenant, default 50"`
|
||||
DefaultImageQuota int `default:"5" help:"Common image quota per tenant, default 5"`
|
||||
DefaultGroupQuota int `default:"50" help:"Common group quota per tenant, default 50"`
|
||||
DefaultSecgroupQuota int `default:"50" help:"Common security group quota per tenant, default 50"`
|
||||
DefaultIsolatedDeviceQuota int `default:"200" help:"Common isolated device quota per tenant, default 200"`
|
||||
|
||||
@@ -58,7 +58,7 @@ func StartService() {
|
||||
cron.AddJob1("CleanPendingDeleteServers", time.Duration(opts.PendingDeleteCheckSeconds)*time.Second, models.GuestManager.CleanPendingDeleteServers)
|
||||
cron.AddJob1("CleanPendingDeleteDisks", time.Duration(opts.PendingDeleteCheckSeconds)*time.Second, models.DiskManager.CleanPendingDeleteDisks)
|
||||
cron.AddJob1("CleanPendingDeleteLoadbalancers", time.Duration(opts.LoadbalancerPendingDeleteCheckInterval)*time.Second, models.LoadbalancerAgentManager.CleanPendingDeleteLoadbalancers)
|
||||
cron.AddJob1("CleanPendingDeleteServers", time.Duration(opts.PrepaidExpireCheckSeconds)*time.Second, models.GuestManager.DeleteExpiredPrepaidServers)
|
||||
cron.AddJob1("CleanExpiredPrepaidServers", time.Duration(opts.PrepaidExpireCheckSeconds)*time.Second, models.GuestManager.DeleteExpiredPrepaidServers)
|
||||
cron.AddJob1("StartHostPingDetectionTask", time.Duration(opts.HostOfflineDetectionInterval)*time.Second, models.HostManager.PingDetectionTask)
|
||||
|
||||
cron.AddJob2("AutoDiskSnapshot", opts.AutoSnapshotDay, opts.AutoSnapshotHour, 0, 0, models.DiskManager.AutoDiskSnapshot, false)
|
||||
|
||||
@@ -83,7 +83,19 @@ func rangeObjHandler(
|
||||
}
|
||||
projectName := json.GetAnyString(getQuery(r), []string{"project", "tenant"})
|
||||
if projectName != "" {
|
||||
userCred, err = generateProjectUserCred(ctx, userCred, projectName)
|
||||
isAllow := false
|
||||
if consts.IsRbacEnabled() {
|
||||
result := policy.PolicyManager.Allow(true, userCred, consts.GetServiceType(),
|
||||
policy.PolicyDelegation, policy.PolicyActionGet)
|
||||
isAllow = result == rbacutils.AdminAllow
|
||||
} else {
|
||||
isAllow = userCred.IsAdminAllow(consts.GetServiceType(), policy.PolicyDelegation, policy.PolicyActionGet)
|
||||
}
|
||||
if !isAllow {
|
||||
httperrors.ForbiddenError(w, "not allow to delegate query usage")
|
||||
return
|
||||
}
|
||||
userCred, err = db.TenantCacheManager.GenerateProjectUserCred(ctx, projectName)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(w, err)
|
||||
return
|
||||
@@ -102,19 +114,6 @@ func rangeObjHandler(
|
||||
}
|
||||
}
|
||||
|
||||
func generateProjectUserCred(ctx context.Context, userCred mcclient.TokenCredential, projectName string) (mcclient.TokenCredential, error) {
|
||||
project, err := db.TenantCacheManager.FetchTenantByIdOrName(ctx, projectName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mcclient.SSimpleToken{
|
||||
Domain: project.Domain,
|
||||
DomainId: project.DomainId,
|
||||
Project: project.Name,
|
||||
ProjectId: project.Id,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func addHandler(prefix, rangeObjKey string, hf appsrv.FilterHandler, app *appsrv.Application) {
|
||||
ahf := auth.Authenticate(hf)
|
||||
name := "get_usage"
|
||||
|
||||
@@ -7,9 +7,13 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/util/httputils"
|
||||
)
|
||||
|
||||
func HTTPError(w http.ResponseWriter, msg string, statusCode int, class string, error httputils.Error) {
|
||||
func SendHTTPErrorHeader(w http.ResponseWriter, statusCode int) {
|
||||
w.WriteHeader(statusCode)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
func HTTPError(w http.ResponseWriter, msg string, statusCode int, class string, error httputils.Error) {
|
||||
SendHTTPErrorHeader(w, statusCode)
|
||||
body := jsonutils.NewDict()
|
||||
body.Add(jsonutils.NewInt(int64(statusCode)), "code")
|
||||
body.Add(jsonutils.NewString(msg), "details")
|
||||
|
||||
44
pkg/image/models/image_members.go
Normal file
44
pkg/image/models/image_members.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package models
|
||||
|
||||
import "yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
|
||||
type SImageMemberManager struct {
|
||||
db.SResourceBaseManager
|
||||
}
|
||||
|
||||
var ImageMemberManager *SImageMemberManager
|
||||
|
||||
func init() {
|
||||
ImageMemberManager = &SImageMemberManager{
|
||||
SResourceBaseManager: db.NewResourceBaseManager(
|
||||
SImageMember{},
|
||||
"image_members",
|
||||
"image_member",
|
||||
"image_members",
|
||||
),
|
||||
}
|
||||
|
||||
ImageMemberManager.TableSpec().AddIndex(true, "image_id", "member")
|
||||
}
|
||||
|
||||
/*
|
||||
+------------+--------------+------+-----+---------+----------------+
|
||||
| Field | Type | Null | Key | Default | Extra |
|
||||
+------------+--------------+------+-----+---------+----------------+
|
||||
| id | int(11) | NO | PRI | NULL | auto_increment |
|
||||
| image_id | varchar(36) | NO | MUL | NULL | |
|
||||
| member | varchar(255) | NO | | NULL | |
|
||||
| can_share | tinyint(1) | NO | | NULL | |
|
||||
| created_at | datetime | NO | | NULL | |
|
||||
| updated_at | datetime | YES | | NULL | |
|
||||
| deleted_at | datetime | YES | | NULL | |
|
||||
| deleted | tinyint(1) | NO | MUL | NULL | |
|
||||
+------------+--------------+------+-----+---------+----------------+
|
||||
*/
|
||||
|
||||
type SImageMember struct {
|
||||
SImagePeripheral
|
||||
|
||||
Member string `width:"255" nullable:"false"`
|
||||
CanShare bool `nullable:"false"`
|
||||
}
|
||||
10
pkg/image/models/image_peripherals.go
Normal file
10
pkg/image/models/image_peripherals.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package models
|
||||
|
||||
import "yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
|
||||
type SImagePeripheral struct {
|
||||
db.SResourceBase
|
||||
|
||||
Id int `primary:"true" auto_increment:"true" nullable:"false"`
|
||||
ImageId string `width:"36" index:"true" nullable:"false"`
|
||||
}
|
||||
124
pkg/image/models/image_properties.go
Normal file
124
pkg/image/models/image_properties.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
)
|
||||
|
||||
type SImagePropertyManager struct {
|
||||
db.SResourceBaseManager
|
||||
}
|
||||
|
||||
var ImagePropertyManager *SImagePropertyManager
|
||||
|
||||
func init() {
|
||||
ImagePropertyManager = &SImagePropertyManager{
|
||||
SResourceBaseManager: db.NewResourceBaseManager(
|
||||
SImageProperty{},
|
||||
"image_properties",
|
||||
"image_property",
|
||||
"image_properties",
|
||||
),
|
||||
}
|
||||
ImagePropertyManager.TableSpec().AddIndex(true, "image_id", "name")
|
||||
}
|
||||
|
||||
/*
|
||||
+------------+--------------+------+-----+---------+----------------+
|
||||
| Field | Type | Null | Key | Default | Extra |
|
||||
+------------+--------------+------+-----+---------+----------------+
|
||||
| id | int(11) | NO | PRI | NULL | auto_increment |
|
||||
| image_id | varchar(36) | NO | MUL | NULL | |
|
||||
| name | varchar(255) | NO | | NULL | |
|
||||
| value | text | YES | | NULL | |
|
||||
| created_at | datetime | NO | | NULL | |
|
||||
| updated_at | datetime | YES | | NULL | |
|
||||
| deleted_at | datetime | YES | | NULL | |
|
||||
| deleted | tinyint(1) | NO | MUL | NULL | |
|
||||
+------------+--------------+------+-----+---------+----------------+
|
||||
*/
|
||||
type SImageProperty struct {
|
||||
SImagePeripheral
|
||||
|
||||
Name string `width:"255"`
|
||||
Value string `nullable:"true" create:"optional"`
|
||||
}
|
||||
|
||||
func (manager *SImagePropertyManager) GetProperties(imageId string) (map[string]string, error) {
|
||||
properties := make([]SImageProperty, 0)
|
||||
q := manager.Query("name", "value").Equals("image_id", imageId)
|
||||
err := db.FetchModelObjects(manager, q, &properties)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
props := make(map[string]string)
|
||||
for i := range properties {
|
||||
props[properties[i].Name] = properties[i].Value
|
||||
}
|
||||
return props, nil
|
||||
}
|
||||
|
||||
func (manager *SImagePropertyManager) SaveProperties(ctx context.Context, userCred mcclient.TokenCredential, imageId string, props jsonutils.JSONObject) error {
|
||||
propsJson := props.(*jsonutils.JSONDict)
|
||||
for _, k := range propsJson.SortedKeys() {
|
||||
v, _ := propsJson.GetString(k)
|
||||
_, err := manager.SaveProperty(ctx, userCred, imageId, k, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (manager *SImagePropertyManager) SaveProperty(ctx context.Context, userCred mcclient.TokenCredential, imageId string, key string, value string) (*SImageProperty, error) {
|
||||
prop, _ := manager.GetProperty(imageId, key)
|
||||
if prop != nil {
|
||||
if prop.Value != value {
|
||||
return prop, prop.UpdateValue(ctx, userCred, value)
|
||||
} else {
|
||||
return prop, nil
|
||||
}
|
||||
} else {
|
||||
// create
|
||||
return manager.NewProperty(ctx, userCred, imageId, key, value)
|
||||
}
|
||||
}
|
||||
|
||||
func (manager *SImagePropertyManager) GetProperty(imageId string, key string) (*SImageProperty, error) {
|
||||
q := manager.Query().Equals("image_id", imageId).Equals("name", key)
|
||||
prop := SImageProperty{}
|
||||
prop.SetModelManager(manager)
|
||||
|
||||
err := q.First(&prop)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &prop, nil
|
||||
}
|
||||
|
||||
func (manager *SImagePropertyManager) NewProperty(ctx context.Context, userCred mcclient.TokenCredential, imageId string, key string, value string) (*SImageProperty, error) {
|
||||
prop := SImageProperty{}
|
||||
prop.ImageId = imageId
|
||||
prop.Name = key
|
||||
prop.Value = value
|
||||
|
||||
err := manager.TableSpec().Insert(&prop)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prop.SetModelManager(manager)
|
||||
return &prop, nil
|
||||
}
|
||||
|
||||
func (self *SImageProperty) UpdateValue(ctx context.Context, userCred mcclient.TokenCredential, value string) error {
|
||||
_, err := self.GetModelManager().TableSpec().Update(self, func() error {
|
||||
self.Value = value
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
213
pkg/image/models/image_subs.go
Normal file
213
pkg/image/models/image_subs.go
Normal file
@@ -0,0 +1,213 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/qemuimg"
|
||||
"fmt"
|
||||
"yunion.io/x/onecloud/pkg/util/fileutils2"
|
||||
"yunion.io/x/onecloud/pkg/image/torrent"
|
||||
"yunion.io/x/onecloud/pkg/util/torrentutils"
|
||||
"os"
|
||||
)
|
||||
|
||||
type SImageSubformatManager struct {
|
||||
db.SResourceBaseManager
|
||||
}
|
||||
|
||||
var ImageSubformatManager *SImageSubformatManager
|
||||
|
||||
func init() {
|
||||
ImageSubformatManager = &SImageSubformatManager{
|
||||
SResourceBaseManager: db.NewResourceBaseManager(
|
||||
SImageSubformat{},
|
||||
"image_subformats",
|
||||
"image_subformat",
|
||||
"image_subformats",
|
||||
),
|
||||
}
|
||||
|
||||
ImageSubformatManager.TableSpec().AddIndex(true, "image_id", "format", "is_torrent")
|
||||
}
|
||||
|
||||
type SImageSubformat struct {
|
||||
SImagePeripheral
|
||||
|
||||
Format string `width:"20" charset:"ascii" nullable:"true"`
|
||||
|
||||
Size int64 `nullable:"false"`
|
||||
Location string `nullable:"false"`
|
||||
Checksum string `width:"32" charset:"ascii" nullable:"true"`
|
||||
Status string `nullable:"false"`
|
||||
|
||||
TorrentSize int64 `nullable:"false"`
|
||||
TorrentLocation string `nullable:"true"`
|
||||
TorrentChecksum string `width:"32" charset:"ascii" nullable:"true"`
|
||||
TorrentStatus string `nullable:"false"`
|
||||
}
|
||||
|
||||
func (manager *SImageSubformatManager) FetchSubImage(id string, format string) *SImageSubformat {
|
||||
q := manager.Query().Equals("image_id", id).Equals("format", format)
|
||||
subImg := SImageSubformat{}
|
||||
err := q.First(&subImg)
|
||||
if err != nil {
|
||||
log.Errorf("query subimage fail!")
|
||||
return nil
|
||||
}
|
||||
return &subImg
|
||||
}
|
||||
|
||||
func (manager *SImageSubformatManager) GetAllSubImages(id string) []SImageSubformat {
|
||||
q := manager.Query().Equals("image_id", id)
|
||||
var subImgs []SImageSubformat
|
||||
err := db.FetchModelObjects(manager, q, &subImgs)
|
||||
if err != nil {
|
||||
log.Errorf("query subimage fail!")
|
||||
return nil
|
||||
}
|
||||
return subImgs
|
||||
}
|
||||
|
||||
func (self *SImageSubformat) DoConvert(image *SImage) error {
|
||||
err := self.Save(image)
|
||||
if err != nil {
|
||||
log.Errorf("fail to convert image %s", err)
|
||||
return err
|
||||
}
|
||||
err = self.SaveTorrent()
|
||||
if err != nil {
|
||||
log.Errorf("fail to convert image torrent %s", err)
|
||||
return err
|
||||
}
|
||||
err = self.seedTorrent()
|
||||
if err != nil {
|
||||
log.Errorf("fail to seed torrent %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SImageSubformat) Save(image *SImage) error {
|
||||
if self.Status == IMAGE_STATUS_ACTIVE {
|
||||
return nil
|
||||
}
|
||||
if self.Status != IMAGE_STATUS_QUEUED {
|
||||
return nil // httperrors.NewInvalidStatusError("cannot save in status %s", self.Status)
|
||||
}
|
||||
location := image.GetPath(self.Format)
|
||||
_, err := self.GetModelManager().TableSpec().Update(self, func() error {
|
||||
self.Status = IMAGE_STATUS_SAVING
|
||||
self.Location = fmt.Sprintf("%s%s", LocalFilePrefix, location)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("updateStatus fail %s", err)
|
||||
return err
|
||||
}
|
||||
img, err := image.getQemuImage()
|
||||
if err != nil {
|
||||
log.Errorf("image.getQemuImage fail %s", err)
|
||||
return err
|
||||
}
|
||||
nimg, err := img.Clone(location, qemuimg.TImageFormat(self.Format), true)
|
||||
if err != nil {
|
||||
log.Errorf("img.Clone fail %s", err)
|
||||
return err
|
||||
}
|
||||
checksum, err := fileutils2.Md5(location)
|
||||
if err != nil {
|
||||
log.Errorf("fileutils2.Md5 fail %s", err)
|
||||
return err
|
||||
}
|
||||
_, err = self.GetModelManager().TableSpec().Update(self, func() error {
|
||||
self.Status = IMAGE_STATUS_ACTIVE
|
||||
self.Location = fmt.Sprintf("%s%s", LocalFilePrefix, location)
|
||||
self.Checksum = checksum
|
||||
self.Size = nimg.ActualSizeBytes
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("updateStatus fail %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SImageSubformat) SaveTorrent() error {
|
||||
if self.TorrentStatus == IMAGE_STATUS_ACTIVE {
|
||||
return nil
|
||||
}
|
||||
if self.TorrentStatus != IMAGE_STATUS_QUEUED {
|
||||
return nil // httperrors.NewInvalidStatusError("cannot save torrent in status %s", self.Status)
|
||||
}
|
||||
imgPath := self.getLocalLocation()
|
||||
torrentPath := fmt.Sprintf("%s.torrent", imgPath)
|
||||
_, err := self.GetModelManager().TableSpec().Update(self, func() error {
|
||||
self.TorrentStatus = IMAGE_STATUS_SAVING
|
||||
self.TorrentLocation = fmt.Sprintf("%s%s", LocalFilePrefix, torrentPath)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("updateStatus fail %s", err)
|
||||
return err
|
||||
}
|
||||
_, err = torrentutils.GenerateTorrent(imgPath, torrent.GetTrackers(), torrentPath)
|
||||
if err != nil {
|
||||
log.Errorf("torrentutils.GenerateTorrent fail %s", err)
|
||||
return err
|
||||
}
|
||||
checksum, err := fileutils2.Md5(torrentPath)
|
||||
if err != nil {
|
||||
log.Errorf("fileutils2.Md5 fail %s", err)
|
||||
return err
|
||||
}
|
||||
_, err = self.GetModelManager().TableSpec().Update(self, func() error {
|
||||
self.TorrentStatus = IMAGE_STATUS_ACTIVE
|
||||
self.TorrentLocation = fmt.Sprintf("%s%s", LocalFilePrefix, torrentPath)
|
||||
self.TorrentChecksum = checksum
|
||||
self.TorrentSize = fileutils2.FileSize(torrentPath)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("updateStatus fail %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SImageSubformat) getLocalLocation() string {
|
||||
return self.Location[len(LocalFilePrefix):]
|
||||
}
|
||||
|
||||
func (self *SImageSubformat) getLocalTorrentLocation() string {
|
||||
return self.TorrentLocation[len(LocalFilePrefix):]
|
||||
}
|
||||
|
||||
func (self *SImageSubformat) seedTorrent() error {
|
||||
file := self.getLocalTorrentLocation()
|
||||
log.Debugf("add torrent %s to seed...", file)
|
||||
return torrent.AddTorrent(file)
|
||||
}
|
||||
|
||||
func (self *SImageSubformat) StopTorrent() {
|
||||
if len(self.TorrentLocation) > 0 {
|
||||
torrent.RemoveTorrent(self.getLocalTorrentLocation())
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SImageSubformat) RemoveFiles() error {
|
||||
self.StopTorrent()
|
||||
if len(self.TorrentLocation) > 0 {
|
||||
err := os.Remove(self.getLocalTorrentLocation())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(self.Location) > 0 {
|
||||
err := os.Remove(self.getLocalLocation())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
40
pkg/image/models/image_tags.go
Normal file
40
pkg/image/models/image_tags.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package models
|
||||
|
||||
import "yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
|
||||
type SImageTagManager struct {
|
||||
db.SResourceBaseManager
|
||||
}
|
||||
|
||||
var ImageTagManager *SImageTagManager
|
||||
|
||||
func init() {
|
||||
ImageTagManager = &SImageTagManager{
|
||||
SResourceBaseManager: db.NewResourceBaseManager(
|
||||
SImageTag{},
|
||||
"image_tags",
|
||||
"image_tag",
|
||||
"image_tags",
|
||||
),
|
||||
}
|
||||
ImageTagManager.TableSpec().AddIndex(true, "image_id", "value")
|
||||
}
|
||||
|
||||
/*
|
||||
+------------+--------------+------+-----+---------+----------------+
|
||||
| Field | Type | Null | Key | Default | Extra |
|
||||
+------------+--------------+------+-----+---------+----------------+
|
||||
| id | int(11) | NO | PRI | NULL | auto_increment |
|
||||
| image_id | varchar(36) | NO | MUL | NULL | |
|
||||
| value | varchar(255) | NO | | NULL | |
|
||||
| created_at | datetime | NO | | NULL | |
|
||||
| updated_at | datetime | YES | | NULL | |
|
||||
| deleted_at | datetime | YES | | NULL | |
|
||||
| deleted | tinyint(1) | NO | | NULL | |
|
||||
+------------+--------------+------+-----+---------+----------------+
|
||||
*/
|
||||
type SImageTag struct {
|
||||
SImagePeripheral
|
||||
|
||||
Value string `width:"255" nullable:"false"`
|
||||
}
|
||||
758
pkg/image/models/images.go
Normal file
758
pkg/image/models/images.go
Normal file
@@ -0,0 +1,758 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/util/timeutils"
|
||||
"yunion.io/x/pkg/utils"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/quotas"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/image/options"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
"yunion.io/x/onecloud/pkg/util/logclient"
|
||||
"yunion.io/x/onecloud/pkg/util/streamutils"
|
||||
"yunion.io/x/pkg/tristate"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/qemuimg"
|
||||
)
|
||||
|
||||
type TImageType string
|
||||
|
||||
const (
|
||||
// https://docs.openstack.org/glance/pike/user/statuses.html
|
||||
//
|
||||
IMAGE_STATUS_QUEUED = "queued"
|
||||
IMAGE_STATUS_SAVING = "saving"
|
||||
IMAGE_STATUS_ACTIVE = "active"
|
||||
IMAGE_STATUS_CONVERTING = "converting"
|
||||
IMAGE_STATUS_DEACTIVATED = "deactivated"
|
||||
IMAGE_STATUS_KILLED = "killed"
|
||||
IMAGE_STATUS_DELETED = "deleted"
|
||||
IMAGE_STATUS_PENDING_DELETE = "pending_delete"
|
||||
|
||||
ImageTypeTemplate = TImageType("image")
|
||||
ImageTypeISO = TImageType("iso")
|
||||
|
||||
LocalFilePrefix = "file://"
|
||||
)
|
||||
|
||||
var (
|
||||
candidateSubImageFormats = []qemuimg.TImageFormat {qemuimg.QCOW2, qemuimg.VMDK}
|
||||
)
|
||||
|
||||
type SImageManager struct {
|
||||
db.SSharableVirtualResourceBaseManager
|
||||
}
|
||||
|
||||
var ImageManager *SImageManager
|
||||
|
||||
var imgStreamingWorkerMan *appsrv.SWorkerManager
|
||||
|
||||
func init() {
|
||||
ImageManager = &SImageManager{
|
||||
SSharableVirtualResourceBaseManager: db.NewSharableVirtualResourceBaseManager(
|
||||
SImage{},
|
||||
"images",
|
||||
"image",
|
||||
"images",
|
||||
),
|
||||
}
|
||||
|
||||
imgStreamingWorkerMan = appsrv.NewWorkerManager("image_streaming_worker", 20, 1024)
|
||||
}
|
||||
|
||||
/*
|
||||
+------------------+--------------+------+-----+---------+-------+
|
||||
| Field | Type | Null | Key | Default | Extra |
|
||||
+------------------+--------------+------+-----+---------+-------+
|
||||
| id | varchar(36) | NO | PRI | NULL | |
|
||||
| name | varchar(255) | YES | | NULL | |
|
||||
| size | bigint(20) | YES | | NULL | |
|
||||
| status | varchar(30) | NO | | NULL | |
|
||||
| is_public | tinyint(1) | NO | MUL | NULL | |
|
||||
| location | text | YES | | NULL | |
|
||||
| created_at | datetime | NO | | NULL | |
|
||||
| updated_at | datetime | YES | | NULL | |
|
||||
| deleted_at | datetime | YES | | NULL | |
|
||||
| deleted | tinyint(1) | NO | MUL | NULL | |
|
||||
| parent_id | varchar(36) | YES | | NULL | |
|
||||
| disk_format | varchar(20) | YES | | NULL | |
|
||||
| container_format | varchar(20) | YES | | NULL | |
|
||||
| checksum | varchar(32) | YES | | NULL | |
|
||||
| owner | varchar(255) | YES | | NULL | |
|
||||
| min_disk | int(11) | NO | | NULL | |
|
||||
| min_ram | int(11) | NO | | NULL | |
|
||||
| protected | tinyint(1) | YES | | NULL | |
|
||||
| description | varchar(256) | YES | | NULL | |
|
||||
+------------------+--------------+------+-----+---------+-------+
|
||||
*/
|
||||
type SImage struct {
|
||||
db.SSharableVirtualResourceBase
|
||||
|
||||
Size int64 `nullable:"true" list:"user" create:"optional"`
|
||||
VirtualSize int64 `nullable:"true" list:"user" create:"optional"`
|
||||
Location string `nullable:"true"`
|
||||
|
||||
DiskFormat string `width:"20" charset:"ascii" nullable:"true" list:"user" create:"optional"` // Column(VARCHAR(32, charset='ascii'), nullable=False, default='qcow2')
|
||||
Checksum string `width:"32" charset:"ascii" nullable:"true" get:"user"`
|
||||
Owner string `width:"255" charset:"ascii" nullable:"true" get:"user"`
|
||||
MinDisk int32 `nullable:"false" default:"0" get:"user" create:"optional" update:"user"`
|
||||
MinRam int32 `nullable:"false" default:"0" get:"user" create:"optional" update:"user"`
|
||||
Protected *bool `nullable:"true" get:"user" create:"optional" update:"user"`
|
||||
}
|
||||
|
||||
func (manager *SImageManager) CustomizeHandlerInfo(info *appsrv.SHandlerInfo) {
|
||||
switch info.GetName(nil) {
|
||||
case "get_details", "create", "update":
|
||||
info.SetProcessTimeout(time.Minute * 30).SetWorkerManager(imgStreamingWorkerMan)
|
||||
}
|
||||
}
|
||||
|
||||
func (manager *SImageManager) FetchCreateHeaderData(ctx context.Context, header http.Header) (jsonutils.JSONObject, error) {
|
||||
return modules.FetchImageMeta(header), nil
|
||||
}
|
||||
|
||||
func (manager *SImageManager) FetchUpdateHeaderData(ctx context.Context, header http.Header) (jsonutils.JSONObject, error) {
|
||||
return modules.FetchImageMeta(header), nil
|
||||
}
|
||||
|
||||
func (manager *SImageManager) InitializeData() error {
|
||||
// set cloudregion ID
|
||||
images := make([]SImage, 0)
|
||||
q := manager.Query().IsNullOrEmpty("tenant_id")
|
||||
err := db.FetchModelObjects(manager, q, &images)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < len(images); i += 1 {
|
||||
if len(images[i].ProjectId) == 0 {
|
||||
manager.TableSpec().Update(&images[i], func() error {
|
||||
images[i].ProjectId = images[i].Owner
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SImage) AllowGetDetailsTorrent(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
|
||||
return self.IsOwner(userCred) || db.IsAdminAllowGetSpec(userCred, self, "torrent")
|
||||
}
|
||||
|
||||
func (self *SImage) GetDetailsTorrent(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (manager *SImageManager) AllowGetPropertyDetail(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (manager *SImageManager) GetPropertyDetail(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
appParams := appsrv.AppContextGetParams(ctx)
|
||||
appParams.OverrideResponseBodyWrapper = true
|
||||
|
||||
queryDict := query.(*jsonutils.JSONDict)
|
||||
queryDict.Add(jsonutils.JSONTrue, "details")
|
||||
|
||||
items, err := db.ListItems(manager, ctx, userCred, queryDict, "")
|
||||
if err != nil {
|
||||
log.Errorf("Fail to list items: %s", err)
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
return modules.ListResult2JSONWithKey(items, manager.KeywordPlural()), nil
|
||||
}
|
||||
|
||||
func (manager *SImageManager) IsCustomizedGetDetailsBody() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *SImage) CustomizedGetDetailsBody(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
appParams := appsrv.AppContextGetParams(ctx)
|
||||
|
||||
filePath := self.GetPath("")
|
||||
fp, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
n, _ := fp.Read(buf)
|
||||
|
||||
if n > 0 {
|
||||
offset := 0
|
||||
for offset < n {
|
||||
m, err := appParams.Response.Write(buf[offset:n])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
offset += m
|
||||
}
|
||||
} else if n == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (self *SImage) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
|
||||
extra := self.SVirtualResourceBase.GetCustomizeColumns(ctx, userCred, query)
|
||||
properties, _ := ImagePropertyManager.GetProperties(self.Id)
|
||||
if len(properties) > 0 {
|
||||
jsonProps := jsonutils.NewDict()
|
||||
for k, v := range properties {
|
||||
jsonProps.Add(jsonutils.NewString(v), k)
|
||||
}
|
||||
extra.Add(jsonProps, "properties")
|
||||
}
|
||||
return extra
|
||||
}
|
||||
|
||||
func (self *SImage) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
|
||||
extra, err := self.SVirtualResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
properties, err := ImagePropertyManager.GetProperties(self.Id)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
propJson := jsonutils.NewDict()
|
||||
for k, v := range properties {
|
||||
propJson.Add(jsonutils.NewString(v), k)
|
||||
}
|
||||
extra.Add(propJson, "properties")
|
||||
|
||||
if self.PendingDeleted {
|
||||
pendingDeletedAt := self.PendingDeletedAt.Add(time.Second * time.Duration(options.Options.PendingDeleteExpireSeconds))
|
||||
extra.Add(jsonutils.NewString(timeutils.FullIsoTime(pendingDeletedAt)), "auto_delete_at")
|
||||
}
|
||||
|
||||
return extra, nil
|
||||
}
|
||||
|
||||
func (self *SImage) GetExtraDetailsHeaders(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) map[string]string {
|
||||
headers := make(map[string]string)
|
||||
|
||||
extra, _ := self.SVirtualResourceBase.GetExtraDetails(ctx, userCred, query)
|
||||
if extra != nil {
|
||||
for _, k := range extra.SortedKeys() {
|
||||
val, _ := extra.GetString(k)
|
||||
if len(val) > 0 {
|
||||
headers[fmt.Sprintf("%s%s", modules.IMAGE_META, k)] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jsonDict := jsonutils.Marshal(self).(*jsonutils.JSONDict)
|
||||
fields := db.GetDetailFields(self.GetModelManager(), userCred)
|
||||
for _, k := range jsonDict.SortedKeys() {
|
||||
if utils.IsInStringArray(k, fields) {
|
||||
val, _ := jsonDict.GetString(k)
|
||||
if len(val) > 0 {
|
||||
headers[fmt.Sprintf("%s%s", modules.IMAGE_META, k)] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
properties, _ := ImagePropertyManager.GetProperties(self.Id)
|
||||
if len(properties) > 0 {
|
||||
for k, v := range properties {
|
||||
headers[fmt.Sprintf("%s%s", modules.IMAGE_META_PROPERTY, k)] = v
|
||||
}
|
||||
}
|
||||
|
||||
if self.PendingDeleted {
|
||||
pendingDeletedAt := self.PendingDeletedAt.Add(time.Second * time.Duration(options.Options.PendingDeleteExpireSeconds))
|
||||
headers[fmt.Sprintf("%s%s", modules.IMAGE_META, "auto_delete_at")] = timeutils.FullIsoTime(pendingDeletedAt)
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
func (manager *SImageManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
|
||||
_, err := manager.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pendingUsage := SQuota{Image: 1}
|
||||
if err := QuotaManager.CheckSetPendingQuota(ctx, userCred, userCred.GetProjectId(), &pendingUsage); err != nil {
|
||||
return nil, httperrors.NewOutOfQuotaError("%s", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (self *SImage) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
|
||||
err := self.SVirtualResourceBase.CustomizeCreate(ctx, userCred, ownerProjId, query, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.Status = IMAGE_STATUS_QUEUED
|
||||
self.Owner = self.ProjectId
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SImage) GetPath(format string) string {
|
||||
path := filepath.Join(options.Options.FilesystemStoreDatadir, self.Id)
|
||||
if len(format) > 0 {
|
||||
path = fmt.Sprintf("%s.%s", path, format)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func (self *SImage) OnSaveFailed(ctx context.Context, userCred mcclient.TokenCredential, msg string) {
|
||||
log.Errorf(msg)
|
||||
self.SetStatus(userCred, IMAGE_STATUS_QUEUED, msg)
|
||||
db.OpsLog.LogEvent(self, db.ACT_SAVE_FAIL, msg, userCred)
|
||||
logclient.AddActionLog(self, logclient.ACT_IMAGE_SAVE, nil, userCred, false)
|
||||
}
|
||||
|
||||
func (self *SImage) OnSaveSuccess(ctx context.Context, userCred mcclient.TokenCredential, msg string) {
|
||||
self.SetStatus(userCred, IMAGE_STATUS_ACTIVE, msg)
|
||||
db.OpsLog.LogEvent(self, db.ACT_SAVE, msg, userCred)
|
||||
logclient.AddActionLog(self, logclient.ACT_IMAGE_SAVE, nil, userCred, true)
|
||||
}
|
||||
|
||||
func (self *SImage) SaveImageFromStream(reader io.Reader) error {
|
||||
fp, err := os.Create(self.GetPath(""))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
sp, err := streamutils.StreamPipe(reader, fp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
virtualSize := int64(0)
|
||||
format := ""
|
||||
img, err := qemuimg.NewQemuImage(self.GetPath(""))
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
format = string(img.Format)
|
||||
virtualSize = img.SizeBytes
|
||||
}
|
||||
|
||||
self.GetModelManager().TableSpec().Update(self, func() error {
|
||||
self.Size = sp.Size
|
||||
self.Checksum = sp.CheckSum
|
||||
self.Location = fmt.Sprintf("%s%s", LocalFilePrefix, self.GetPath(""))
|
||||
if len(format) > 0 {
|
||||
self.DiskFormat = format
|
||||
}
|
||||
if virtualSize > 0 {
|
||||
self.VirtualSize = virtualSize
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SImage) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data jsonutils.JSONObject) {
|
||||
self.SVirtualResourceBase.PostCreate(ctx, userCred, ownerProjId, query, data)
|
||||
|
||||
pendingUsage := SQuota{Image: 1}
|
||||
QuotaManager.CancelPendingUsage(ctx, userCred, userCred.GetProjectId(), &pendingUsage, &pendingUsage)
|
||||
|
||||
if data.Contains("properties") {
|
||||
// update properties
|
||||
props, _ := data.Get("properties")
|
||||
err := ImagePropertyManager.SaveProperties(ctx, userCred, self.Id, props)
|
||||
if err != nil {
|
||||
log.Warningf("save properties error %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
appParams := appsrv.AppContextGetParams(ctx)
|
||||
if appParams.Request.ContentLength > 0 {
|
||||
db.OpsLog.LogEvent(self, db.ACT_SAVING, "create upload", userCred)
|
||||
self.SetStatus(userCred, IMAGE_STATUS_SAVING, "create upload")
|
||||
|
||||
err := self.SaveImageFromStream(appParams.Request.Body)
|
||||
if err != nil {
|
||||
self.OnSaveFailed(ctx, userCred, fmt.Sprintf("create upload fail %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
self.OnSaveSuccess(ctx, userCred, "create upload success")
|
||||
|
||||
self.StartImageConvertTask(ctx, userCred, "")
|
||||
} else {
|
||||
copyFrom := appParams.Request.Header.Get(modules.IMAGE_META_COPY_FROM)
|
||||
if len(copyFrom) > 0 {
|
||||
self.startImageCopyFromUrlTask(ctx, userCred, copyFrom, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SImage) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
|
||||
if self.Status != IMAGE_STATUS_QUEUED {
|
||||
appParams := appsrv.AppContextGetParams(ctx)
|
||||
if appParams != nil && appParams.Request.ContentLength > 0 {
|
||||
return nil, httperrors.NewInvalidStatusError("cannot upload in status %s", self.Status)
|
||||
}
|
||||
} else {
|
||||
appParams := appsrv.AppContextGetParams(ctx)
|
||||
if appParams != nil {
|
||||
if appParams.Request.ContentLength > 0 {
|
||||
self.SetStatus(userCred, IMAGE_STATUS_SAVING, "update start upload")
|
||||
err := self.SaveImageFromStream(appParams.Request.Body)
|
||||
if err != nil {
|
||||
self.OnSaveFailed(ctx, userCred, fmt.Sprintf("update upload failed %s", err))
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
self.OnSaveSuccess(ctx, userCred, "update upload success")
|
||||
data.Remove("status")
|
||||
} else {
|
||||
copyFrom := appParams.Request.Header.Get(modules.IMAGE_META_COPY_FROM)
|
||||
if len(copyFrom) > 0 {
|
||||
err := self.startImageCopyFromUrlTask(ctx, userCred, copyFrom, "")
|
||||
if err != nil {
|
||||
self.OnSaveFailed(ctx, userCred, fmt.Sprintf("update copy from url failed %s", err))
|
||||
return nil, httperrors.NewGeneralError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return self.SVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, data)
|
||||
}
|
||||
|
||||
func (self *SImage) PreUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
|
||||
self.SVirtualResourceBase.PreUpdate(ctx, userCred, query, data)
|
||||
}
|
||||
|
||||
func (self *SImage) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
|
||||
self.SVirtualResourceBase.PostUpdate(ctx, userCred, query, data)
|
||||
|
||||
if data.Contains("properties") {
|
||||
// update properties
|
||||
props, _ := data.Get("properties")
|
||||
err := ImagePropertyManager.SaveProperties(ctx, userCred, self.Id, props)
|
||||
if err != nil {
|
||||
log.Errorf("save properties error %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SImage) ValidateDeleteCondition(ctx context.Context) error {
|
||||
if self.IsPublic {
|
||||
return httperrors.NewInvalidStatusError("image is shared")
|
||||
}
|
||||
if self.Protected != nil && *self.Protected {
|
||||
return httperrors.NewForbiddenError("image is protected")
|
||||
}
|
||||
return self.SVirtualResourceBase.ValidateDeleteCondition(ctx)
|
||||
}
|
||||
|
||||
func (self *SImage) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
|
||||
log.Infof("image delete do nothing")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SImage) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
|
||||
return self.SVirtualResourceBase.Delete(ctx, userCred)
|
||||
}
|
||||
|
||||
func (self *SImage) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
|
||||
overridePendingDelete := false
|
||||
purge := false
|
||||
if query != nil {
|
||||
overridePendingDelete = jsonutils.QueryBoolean(query, "override_pending_delete", false)
|
||||
purge = jsonutils.QueryBoolean(query, "purge", false)
|
||||
}
|
||||
return self.startDeleteImageTask(ctx, userCred, "", purge, overridePendingDelete)
|
||||
}
|
||||
|
||||
func (self *SImage) startDeleteImageTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, isPurge bool, overridePendingDelete bool) error {
|
||||
params := jsonutils.NewDict()
|
||||
if isPurge {
|
||||
params.Add(jsonutils.JSONTrue, "purge")
|
||||
}
|
||||
if overridePendingDelete {
|
||||
params.Add(jsonutils.JSONTrue, "override_pending_delete")
|
||||
}
|
||||
params.Add(jsonutils.NewString(self.Status), "image_status")
|
||||
|
||||
self.SetStatus(userCred, IMAGE_STATUS_DEACTIVATED, "")
|
||||
|
||||
task, err := taskman.TaskManager.NewTask(ctx, "ImageDeleteTask", self, userCred, params, parentTaskId, "", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
task.ScheduleRun(nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SImage) startImageCopyFromUrlTask(ctx context.Context, userCred mcclient.TokenCredential, copyFrom string, parentTaskId string) error {
|
||||
params := jsonutils.NewDict()
|
||||
params.Add(jsonutils.NewString(copyFrom), "copy_from")
|
||||
|
||||
msg := fmt.Sprintf("copy from url %s", copyFrom)
|
||||
self.SetStatus(userCred, IMAGE_STATUS_SAVING, msg)
|
||||
db.OpsLog.LogEvent(self, db.ACT_SAVING, msg, userCred)
|
||||
|
||||
task, err := taskman.TaskManager.NewTask(ctx, "ImageCopyFromUrlTask", self, userCred, params, parentTaskId, "", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
task.ScheduleRun(nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SImage) StartImageConvertTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
|
||||
task, err := taskman.TaskManager.NewTask(ctx, "ImageConvertTask", self, userCred, nil, parentTaskId, "", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
task.ScheduleRun(nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SImage) AllowPerformCancelDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return db.IsAdminAllowPerform(userCred, self, "cancel-delete")
|
||||
}
|
||||
|
||||
func (self *SImage) PerformCancelDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
if self.PendingDeleted {
|
||||
err := self.DoCancelPendingDelete(ctx, userCred)
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (manager *SImageManager) getExpiredPendingDeleteDisks() []SImage {
|
||||
deadline := time.Now().Add(time.Duration(options.Options.PendingDeleteExpireSeconds * -1) * time.Second)
|
||||
|
||||
q := manager.Query()
|
||||
q = q.IsTrue("pending_deleted").LT("pending_deleted_at", deadline).Limit(options.Options.PendingDeleteMaxCleanBatchSize)
|
||||
|
||||
disks := make([]SImage, 0)
|
||||
err := db.FetchModelObjects(ImageManager, q, &disks)
|
||||
if err != nil {
|
||||
log.Errorf("fetch disks error %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return disks
|
||||
}
|
||||
|
||||
func (manager *SImageManager) CleanPendingDeleteImages(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
|
||||
disks := manager.getExpiredPendingDeleteDisks()
|
||||
if disks == nil {
|
||||
return
|
||||
}
|
||||
for i := 0; i < len(disks); i += 1 {
|
||||
disks[i].startDeleteImageTask(ctx, userCred, "", false, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SImage) DoPendingDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
|
||||
err := self.SVirtualResourceBase.DoPendingDelete(ctx, userCred)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = self.GetModelManager().TableSpec().Update(self, func() error {
|
||||
self.Status = IMAGE_STATUS_PENDING_DELETE
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *SImage) DoCancelPendingDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
|
||||
err := self.SVirtualResourceBase.DoCancelPendingDelete(ctx, userCred)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = self.GetModelManager().TableSpec().Update(self, func() error {
|
||||
self.Status = IMAGE_STATUS_ACTIVE
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
type SImageUsage struct {
|
||||
Count int64
|
||||
Size int64
|
||||
}
|
||||
|
||||
func (manager *SImageManager) count(projectId string, isISO tristate.TriState, pendingDelete bool) SImageUsage {
|
||||
images := manager.Query().SubQuery()
|
||||
q := images.Query(sqlchemy.COUNT("count"), sqlchemy.SUM("size", images.Field("size")))
|
||||
if len(projectId) > 0 {
|
||||
q = q.Equals("tenant_id", projectId)
|
||||
}
|
||||
if pendingDelete {
|
||||
q = q.IsTrue("pending_deleted")
|
||||
} else {
|
||||
q = q.IsFalse("pending_deleted")
|
||||
}
|
||||
if isISO.IsTrue() {
|
||||
q = q.Equals("disk_format", "iso")
|
||||
} else if isISO.IsFalse() {
|
||||
q = q.NotEquals("disk_format", "iso")
|
||||
}
|
||||
usage := SImageUsage{}
|
||||
q.First(&usage)
|
||||
return usage
|
||||
}
|
||||
|
||||
func (manager *SImageManager) Usage(projectId string, prefix string) map[string]int64 {
|
||||
usages := make(map[string]int64)
|
||||
count := manager.count(projectId, tristate.False, false)
|
||||
usages[quotas.KeyName(prefix, "img.count")] = count.Count
|
||||
usages[quotas.KeyName(prefix, "img.size")] = count.Size
|
||||
count = manager.count(projectId, tristate.True, false)
|
||||
usages[quotas.KeyName(prefix, "iso.count")] = count.Count
|
||||
usages[quotas.KeyName(prefix, "iso.size")] = count.Size
|
||||
count = manager.count(projectId, tristate.False, true)
|
||||
usages[quotas.KeyName(prefix, "img.pending_delete.count")] = count.Count
|
||||
usages[quotas.KeyName(prefix, "img.pending_delete.size")] = count.Size
|
||||
count = manager.count(projectId, tristate.True, true)
|
||||
usages[quotas.KeyName(prefix, "iso.pending_delete.count")] = count.Count
|
||||
usages[quotas.KeyName(prefix, "iso.pending_delete.size")] = count.Size
|
||||
return usages
|
||||
}
|
||||
|
||||
func (self *SImage) GetImageType() TImageType {
|
||||
if self.DiskFormat == string(qemuimg.ISO) {
|
||||
return ImageTypeISO
|
||||
} else {
|
||||
return ImageTypeTemplate
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SImage) MigrateSubImage() error {
|
||||
subimg := ImageSubformatManager.FetchSubImage(self.Id, self.DiskFormat)
|
||||
if subimg != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
subformat := SImageSubformat{}
|
||||
subformat.SetModelManager(ImageSubformatManager)
|
||||
|
||||
subformat.ImageId = self.Id
|
||||
|
||||
subformat.Format = self.DiskFormat
|
||||
|
||||
subformat.Size = self.Size
|
||||
subformat.Checksum = self.Checksum
|
||||
subformat.Status = IMAGE_STATUS_ACTIVE
|
||||
subformat.Location = self.Location
|
||||
|
||||
subformat.TorrentStatus = IMAGE_STATUS_QUEUED
|
||||
|
||||
err := ImageSubformatManager.TableSpec().Insert(&subformat)
|
||||
if err != nil {
|
||||
log.Errorf("fail to make subformat")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SImage) MakeSubImages() error {
|
||||
if self.GetImageType() == ImageTypeISO {
|
||||
return nil
|
||||
}
|
||||
for _, format := range candidateSubImageFormats {
|
||||
if string(format) != self.DiskFormat {
|
||||
// need to create a record
|
||||
subformat := ImageSubformatManager.FetchSubImage(self.Id, string(format))
|
||||
if subformat == nil {
|
||||
subformat := &SImageSubformat{}
|
||||
subformat.SetModelManager(ImageSubformatManager)
|
||||
|
||||
subformat.ImageId = self.Id
|
||||
subformat.Format = string(format)
|
||||
subformat.Status = IMAGE_STATUS_QUEUED
|
||||
subformat.TorrentStatus = IMAGE_STATUS_QUEUED
|
||||
|
||||
err := ImageSubformatManager.TableSpec().Insert(subformat)
|
||||
if err != nil {
|
||||
log.Errorf("fail to make subformat %s", format)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SImage) ConvertAllSubformats() error {
|
||||
subimgs := ImageSubformatManager.GetAllSubImages(self.Id)
|
||||
for i := 0; i < len(subimgs); i += 1 {
|
||||
err := subimgs[i].DoConvert(self)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SImage) getLocalLocation() string {
|
||||
return self.Location[len(LocalFilePrefix):]
|
||||
}
|
||||
|
||||
func (self *SImage) getQemuImage() (*qemuimg.SQemuImage, error) {
|
||||
return qemuimg.NewQemuImage(self.getLocalLocation())
|
||||
}
|
||||
|
||||
func (self *SImage) StopTorrents() {
|
||||
subimgs := ImageSubformatManager.GetAllSubImages(self.Id)
|
||||
for i := 0; i < len(subimgs); i += 1 {
|
||||
subimgs[i].StopTorrent()
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SImage) RemoveFiles() error {
|
||||
subimgs := ImageSubformatManager.GetAllSubImages(self.Id)
|
||||
for i := 0; i < len(subimgs); i += 1 {
|
||||
subimgs[i].StopTorrent()
|
||||
err := subimgs[i].RemoveFiles()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return os.Remove(self.getLocalLocation())
|
||||
}
|
||||
|
||||
func (manager *SImageManager) getAllActiveImages() []SImage {
|
||||
images := make([]SImage, 0)
|
||||
q := manager.Query().Equals("status", IMAGE_STATUS_ACTIVE)
|
||||
err := db.FetchModelObjects(manager, q, &images)
|
||||
if err != nil {
|
||||
log.Errorf("fail to query active images %s", err)
|
||||
return nil
|
||||
}
|
||||
return images
|
||||
}
|
||||
|
||||
func SeedTorrents() {
|
||||
images := ImageManager.getAllActiveImages()
|
||||
for i := 0; i < len(images); i += 1 {
|
||||
log.Debugf("convert image subformats %s", images[i].Name)
|
||||
// images[i].StartImageConvertTask(context.TODO(), auth.AdminCredential(), "")
|
||||
}
|
||||
}
|
||||
23
pkg/image/models/initdb.go
Normal file
23
pkg/image/models/initdb.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
)
|
||||
|
||||
func InitDB() error {
|
||||
for _, manager := range []db.IModelManager{
|
||||
/*
|
||||
* Important!!!
|
||||
* initialization order matters, do not change the order
|
||||
*/
|
||||
ImageManager,
|
||||
} {
|
||||
err := manager.InitializeData()
|
||||
if err != nil {
|
||||
log.Errorf("Manager %s initializeData fail %s", manager.Keyword(), err)
|
||||
// return err skip error table
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
79
pkg/image/models/quotas.go
Normal file
79
pkg/image/models/quotas.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/tristate"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/quotas"
|
||||
"yunion.io/x/onecloud/pkg/image/options"
|
||||
)
|
||||
|
||||
var QuotaManager *quotas.SQuotaManager
|
||||
|
||||
func init() {
|
||||
dbStore := quotas.NewDBQuotaStore()
|
||||
pendingStore := quotas.NewMemoryQuotaStore()
|
||||
|
||||
QuotaManager = quotas.NewQuotaManager("image-quotas", SQuota{}, dbStore, pendingStore)
|
||||
}
|
||||
|
||||
var (
|
||||
ErrOutOfImage = errors.New("out of image quota")
|
||||
)
|
||||
|
||||
type SQuota struct {
|
||||
Image int
|
||||
}
|
||||
|
||||
func (self *SQuota) FetchSystemQuota() {
|
||||
self.Image = options.Options.DefaultImageQuota
|
||||
}
|
||||
|
||||
func (self *SQuota) FetchUsage(projectId string) error {
|
||||
count := ImageManager.count(projectId, tristate.None, false)
|
||||
self.Image = int(count.Count)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SQuota) IsEmpty() bool {
|
||||
if self.Image > 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *SQuota) Add(quota quotas.IQuota) {
|
||||
squota := quota.(*SQuota)
|
||||
self.Image = self.Image + squota.Image
|
||||
}
|
||||
|
||||
func (self *SQuota) Sub(quota quotas.IQuota) {
|
||||
squota := quota.(*SQuota)
|
||||
self.Image = quotas.NonNegative(self.Image - squota.Image)
|
||||
}
|
||||
|
||||
func (self *SQuota) Update(quota quotas.IQuota) {
|
||||
squota := quota.(*SQuota)
|
||||
if squota.Image > 0 {
|
||||
self.Image = squota.Image
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SQuota) Exceed(request quotas.IQuota, quota quotas.IQuota) error {
|
||||
sreq := request.(*SQuota)
|
||||
squota := quota.(*SQuota)
|
||||
if sreq.Image > 0 && self.Image > squota.Image {
|
||||
return ErrOutOfImage
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SQuota) ToJSON(prefix string) jsonutils.JSONObject {
|
||||
ret := jsonutils.NewDict()
|
||||
if self.Image > 0 {
|
||||
ret.Add(jsonutils.NewInt(int64(self.Image)), quotas.KeyName(prefix, "image"))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
24
pkg/image/options/options.go
Normal file
24
pkg/image/options/options.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/pending_delete"
|
||||
)
|
||||
|
||||
type SImageOptions struct {
|
||||
cloudcommon.DBOptions
|
||||
|
||||
pending_delete.SPendingDeleteOptions
|
||||
|
||||
DefaultImageQuota int `default:"5" help:"Common image quota per tenant, default 5"`
|
||||
|
||||
PortV2 int `help:"Listening port for region V2"`
|
||||
|
||||
FilesystemStoreDatadir string `help:"Directory that the Filesystem backend store writes image data to"`
|
||||
|
||||
TorrentStoreDir string `help:"directory to store image torrent files"`
|
||||
}
|
||||
|
||||
var (
|
||||
Options SImageOptions
|
||||
)
|
||||
46
pkg/image/service/handlers.go
Normal file
46
pkg/image/service/handlers.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/appsrv/dispatcher"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/quotas"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||||
"yunion.io/x/onecloud/pkg/image/models"
|
||||
"yunion.io/x/onecloud/pkg/image/usages"
|
||||
)
|
||||
|
||||
const (
|
||||
API_VERSION = "v1"
|
||||
)
|
||||
|
||||
func initHandlers(app *appsrv.Application) {
|
||||
db.InitAllManagers()
|
||||
|
||||
quotas.AddQuotaHandler(models.QuotaManager, API_VERSION, app)
|
||||
usages.AddUsageHandler(API_VERSION, app)
|
||||
taskman.AddTaskHandler(API_VERSION, app)
|
||||
|
||||
for _, manager := range []db.IModelManager{
|
||||
taskman.TaskManager,
|
||||
taskman.SubTaskManager,
|
||||
taskman.TaskObjectManager,
|
||||
// db.UserCacheManager,
|
||||
db.TenantCacheManager,
|
||||
db.Metadata,
|
||||
models.ImageTagManager,
|
||||
models.ImageMemberManager,
|
||||
models.ImagePropertyManager,
|
||||
} {
|
||||
db.RegisterModelManager(manager)
|
||||
}
|
||||
|
||||
for _, manager := range []db.IModelManager{
|
||||
db.OpsLog,
|
||||
models.ImageManager,
|
||||
} {
|
||||
db.RegisterModelManager(manager)
|
||||
handler := db.NewModelHandler(manager)
|
||||
dispatcher.AddModelDispatcher(API_VERSION, app, handler)
|
||||
}
|
||||
}
|
||||
69
pkg/image/service/service.go
Normal file
69
pkg/image/service/service.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
|
||||
"yunion.io/x/log"
|
||||
|
||||
_ "yunion.io/x/onecloud/pkg/image/tasks"
|
||||
|
||||
"time"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/consts"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/cronman"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/image/models"
|
||||
"yunion.io/x/onecloud/pkg/image/options"
|
||||
"yunion.io/x/onecloud/pkg/image/torrent"
|
||||
)
|
||||
|
||||
const (
|
||||
SERVICE_TYPE = "image"
|
||||
)
|
||||
|
||||
func StartService() {
|
||||
consts.SetServiceType(SERVICE_TYPE)
|
||||
|
||||
cloudcommon.ParseOptions(&options.Options, &options.Options.Options, os.Args, "glance-api.conf")
|
||||
|
||||
if options.Options.PortV2 > 0 {
|
||||
log.Infof("Port V2 %d is specified, use v2 port", options.Options.PortV2)
|
||||
options.Options.Port = options.Options.PortV2
|
||||
}
|
||||
|
||||
cloudcommon.InitAuth(&options.Options.Options, func() {
|
||||
log.Infof("Auth complete!!")
|
||||
})
|
||||
|
||||
cloudcommon.InitDB(&options.Options.DBOptions)
|
||||
defer cloudcommon.CloseDB()
|
||||
|
||||
app := cloudcommon.InitApp(&options.Options.Options)
|
||||
initHandlers(app)
|
||||
|
||||
err := torrent.InitTorrentClient()
|
||||
if err != nil {
|
||||
log.Errorf("fail to initialize torrent client: %s", err)
|
||||
return
|
||||
}
|
||||
defer torrent.CloseTorrentClient()
|
||||
|
||||
if !db.CheckSync(options.Options.AutoSyncTable) {
|
||||
log.Errorf("check sync failed")
|
||||
return
|
||||
}
|
||||
|
||||
models.InitDB()
|
||||
|
||||
models.SeedTorrents()
|
||||
|
||||
cron := cronman.GetCronJobManager()
|
||||
cron.AddJob1("CleanPendingDeleteServers", time.Duration(options.Options.PendingDeleteCheckSeconds)*time.Second, models.ImageManager.CleanPendingDeleteImages)
|
||||
|
||||
cron.Start()
|
||||
defer cron.Stop()
|
||||
|
||||
cloudcommon.ServeForever(app, &options.Options.Options)
|
||||
}
|
||||
57
pkg/image/tasks/image_convert_task.go
Normal file
57
pkg/image/tasks/image_convert_task.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package tasks
|
||||
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||||
"yunion.io/x/onecloud/pkg/image/models"
|
||||
)
|
||||
|
||||
type ImageConvertTask struct {
|
||||
taskman.STask
|
||||
}
|
||||
|
||||
func init() {
|
||||
taskman.RegisterTask(ImageConvertTask{})
|
||||
}
|
||||
|
||||
func (self *ImageConvertTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
|
||||
image := obj.(*models.SImage)
|
||||
|
||||
err := self.prepareConvert(image)
|
||||
if err != nil {
|
||||
self.SetStageFailed(ctx, "fail to convert subimages")
|
||||
return
|
||||
}
|
||||
self.SetStage("OnConvertComplete", nil)
|
||||
taskman.LocalTaskRun(self, func() (jsonutils.JSONObject, error) {
|
||||
image.SetStatus(self.UserCred, models.IMAGE_STATUS_CONVERTING, "start convert")
|
||||
defer image.SetStatus(self.UserCred, models.IMAGE_STATUS_ACTIVE, "convert failed")
|
||||
err := image.ConvertAllSubformats()
|
||||
return nil, err
|
||||
})
|
||||
}
|
||||
|
||||
func (self *ImageConvertTask) prepareConvert(image *models.SImage) error {
|
||||
err := image.MigrateSubImage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = image.MakeSubImages()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ImageConvertTask) OnConvertComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
|
||||
self.SetStageComplete(ctx, nil)
|
||||
}
|
||||
|
||||
func (self *ImageConvertTask) OnConvertCompleteFailed(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
|
||||
self.SetStageFailed(ctx, data.String())
|
||||
}
|
||||
52
pkg/image/tasks/image_copy_from_url_task.go
Normal file
52
pkg/image/tasks/image_copy_from_url_task.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"fmt"
|
||||
"net/http"
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||||
"yunion.io/x/onecloud/pkg/image/models"
|
||||
"yunion.io/x/onecloud/pkg/util/httputils"
|
||||
)
|
||||
|
||||
type ImageCopyFromUrlTask struct {
|
||||
taskman.STask
|
||||
}
|
||||
|
||||
func init() {
|
||||
taskman.RegisterTask(ImageCopyFromUrlTask{})
|
||||
}
|
||||
|
||||
func (self *ImageCopyFromUrlTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
|
||||
image := obj.(*models.SImage)
|
||||
|
||||
copyFrom, _ := self.Params.GetString("copy_from")
|
||||
|
||||
header := http.Header{}
|
||||
header.Set("Content-Type", "application/octet-stream")
|
||||
resp, err := httputils.Request(nil, ctx, httputils.GET, copyFrom, header, nil, false)
|
||||
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("copy from url %s request fail %s", copyFrom, err)
|
||||
image.OnSaveFailed(ctx, self.UserCred, msg)
|
||||
self.SetStageFailed(ctx, msg)
|
||||
return
|
||||
}
|
||||
|
||||
err = image.SaveImageFromStream(resp.Body)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf(" copy from url %s stream fail %s", copyFrom, err)
|
||||
image.OnSaveFailed(ctx, self.UserCred, msg)
|
||||
self.SetStageFailed(ctx, msg)
|
||||
return
|
||||
}
|
||||
|
||||
image.OnSaveSuccess(ctx, self.UserCred, "copy from success")
|
||||
|
||||
image.StartImageConvertTask(ctx, self.UserCred, "")
|
||||
|
||||
self.SetStageComplete(ctx, nil)
|
||||
}
|
||||
58
pkg/image/tasks/image_delete_task.go
Normal file
58
pkg/image/tasks/image_delete_task.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||||
"yunion.io/x/onecloud/pkg/image/models"
|
||||
"yunion.io/x/onecloud/pkg/image/options"
|
||||
)
|
||||
|
||||
type ImageDeleteTask struct {
|
||||
taskman.STask
|
||||
}
|
||||
|
||||
func init() {
|
||||
taskman.RegisterTask(ImageDeleteTask{})
|
||||
}
|
||||
|
||||
func (self *ImageDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
|
||||
image := obj.(*models.SImage)
|
||||
|
||||
imageStatus, _ := self.Params.GetString("image_status")
|
||||
isPurge := jsonutils.QueryBoolean(self.Params, "purge", false)
|
||||
isOverridePendingDelete := jsonutils.QueryBoolean(self.Params, "override_pending_delete", false)
|
||||
if options.Options.EnablePendingDelete && !image.PendingDeleted && imageStatus == models.IMAGE_STATUS_ACTIVE && !isPurge && !isOverridePendingDelete {
|
||||
self.startPendingDeleteImage(ctx, image)
|
||||
} else {
|
||||
self.startDeleteImage(ctx, image)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ImageDeleteTask) startPendingDeleteImage(ctx context.Context, image *models.SImage) {
|
||||
image.StopTorrents()
|
||||
image.DoPendingDelete(ctx, self.UserCred)
|
||||
self.SetStageComplete(ctx, nil)
|
||||
}
|
||||
|
||||
func (self *ImageDeleteTask) startDeleteImage(ctx context.Context, image *models.SImage) {
|
||||
log.Debugf("Delete image ....######")
|
||||
|
||||
err := image.RemoveFiles()
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("fail to remove %s %s", image.GetPath(""), err)
|
||||
log.Errorf(msg)
|
||||
self.SetStageFailed(ctx, msg)
|
||||
return
|
||||
}
|
||||
|
||||
image.SetStatus(self.UserCred, models.IMAGE_STATUS_DELETED, "delete")
|
||||
|
||||
image.RealDelete(ctx, self.UserCred)
|
||||
|
||||
self.SetStageComplete(ctx, nil)
|
||||
}
|
||||
85
pkg/image/torrent/torrent.go
Normal file
85
pkg/image/torrent/torrent.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package torrent
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
|
||||
"yunion.io/x/log"
|
||||
"fmt"
|
||||
"yunion.io/x/onecloud/pkg/image/options"
|
||||
"github.com/anacrolix/torrent"
|
||||
"github.com/anacrolix/torrent/metainfo"
|
||||
)
|
||||
|
||||
var (
|
||||
torrentClient *torrent.Client
|
||||
torrentTable = make(map[string]*torrent.Torrent)
|
||||
)
|
||||
|
||||
func GetTrackers() []string {
|
||||
urls, err := auth.GetServiceURLs("torrent-tracker", options.Options.Region, "", "")
|
||||
if err != nil {
|
||||
log.Errorf("fail to get torrent-tracker")
|
||||
return nil
|
||||
}
|
||||
return urls
|
||||
}
|
||||
|
||||
func InitTorrentClient() error {
|
||||
urls := GetTrackers()
|
||||
if len(urls) == 0 {
|
||||
log.Errorf("no valid torrent-tracker")
|
||||
return fmt.Errorf("no valid torrent-tracker")
|
||||
}
|
||||
|
||||
clientConfig := torrent.NewDefaultClientConfig()
|
||||
clientConfig.Debug = false
|
||||
clientConfig.Seed = true
|
||||
clientConfig.DataDir = options.Options.FilesystemStoreDatadir
|
||||
clientConfig.DisableTrackers = false
|
||||
clientConfig.DisablePEX = false
|
||||
clientConfig.NoDHT = true
|
||||
|
||||
client, err := torrent.NewClient(clientConfig)
|
||||
if err != nil {
|
||||
log.Errorf("error creating client: %s", err)
|
||||
return err
|
||||
}
|
||||
torrentClient = client
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CloseTorrentClient() {
|
||||
if torrentClient != nil {
|
||||
torrentClient.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func AddTorrent(filepath string) error {
|
||||
mi, err := metainfo.LoadFromFile(filepath)
|
||||
if err != nil {
|
||||
log.Errorf("fail to open torrent file %s", err)
|
||||
return err
|
||||
}
|
||||
t, err := torrentClient.AddTorrent(mi)
|
||||
if err != nil {
|
||||
log.Errorf("AddTorrent fail %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
torrentTable[filepath] = t
|
||||
|
||||
go func() {
|
||||
<-t.GotInfo()
|
||||
t.DownloadAll()
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RemoveTorrent(filepath string) {
|
||||
if t, ok := torrentTable[filepath]; ok {
|
||||
t.Drop()
|
||||
delete(torrentTable, filepath)
|
||||
}
|
||||
}
|
||||
99
pkg/image/usages/handler.go
Normal file
99
pkg/image/usages/handler.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package usages
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/consts"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/policy"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/image/models"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
"yunion.io/x/onecloud/pkg/util/rbacutils"
|
||||
)
|
||||
|
||||
func AddUsageHandler(prefix string, app *appsrv.Application) {
|
||||
prefix = fmt.Sprintf("%s/usages", prefix)
|
||||
app.AddHandler2("GET", prefix, auth.Authenticate(ReportGeneralUsage), nil, "get_usage", nil)
|
||||
}
|
||||
|
||||
func ReportGeneralUsage(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
_, query, _ := appsrv.FetchEnv(ctx, w, r)
|
||||
userCred := auth.FetchUserCredential(ctx, policy.FilterPolicyCredential)
|
||||
|
||||
projectName := jsonutils.GetAnyString(query, []string{"project", "tenant"})
|
||||
if projectName != "" {
|
||||
isAllow := false
|
||||
if consts.IsRbacEnabled() {
|
||||
result := policy.PolicyManager.Allow(true, userCred, consts.GetServiceType(),
|
||||
policy.PolicyDelegation, policy.PolicyActionGet)
|
||||
isAllow = result == rbacutils.AdminAllow
|
||||
} else {
|
||||
isAllow = userCred.IsAdminAllow(consts.GetServiceType(), policy.PolicyDelegation, policy.PolicyActionGet)
|
||||
}
|
||||
if !isAllow {
|
||||
httperrors.ForbiddenError(w, "not allow to delegate query usage")
|
||||
return
|
||||
}
|
||||
var err error
|
||||
userCred, err = db.TenantCacheManager.GenerateProjectUserCred(ctx, projectName)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
isAdmin := false
|
||||
if consts.IsRbacEnabled() {
|
||||
if policy.PolicyManager.Allow(true, userCred, consts.GetServiceType(),
|
||||
"usages", policy.PolicyActionGet) == rbacutils.AdminAllow {
|
||||
isAdmin = true
|
||||
}
|
||||
} else {
|
||||
isAdmin = userCred.IsAdminAllow(consts.GetServiceType(), "usages", policy.PolicyActionGet)
|
||||
}
|
||||
|
||||
var adminUsage map[string]int64
|
||||
var projectUsage map[string]int64
|
||||
if isAdmin {
|
||||
adminUsage = models.ImageManager.Usage(userCred.GetProjectId(), "all")
|
||||
}
|
||||
|
||||
isProject := false
|
||||
if consts.IsRbacEnabled() {
|
||||
if policy.PolicyManager.Allow(false, userCred, consts.GetServiceType(),
|
||||
"usages", policy.PolicyActionGet) == rbacutils.Deny {
|
||||
isProject = false
|
||||
} else {
|
||||
isProject = true
|
||||
}
|
||||
} else {
|
||||
isProject = true
|
||||
}
|
||||
|
||||
if isProject {
|
||||
projectUsage = models.ImageManager.Usage(userCred.GetProjectId(), "")
|
||||
}
|
||||
|
||||
if !isAdmin && !isProject {
|
||||
httperrors.ForbiddenError(w, "not allow to get usage")
|
||||
return
|
||||
}
|
||||
|
||||
usages := jsonutils.NewDict()
|
||||
if isProject {
|
||||
usages.Update(jsonutils.Marshal(projectUsage))
|
||||
}
|
||||
if isAdmin {
|
||||
usages.Update(jsonutils.Marshal(adminUsage))
|
||||
}
|
||||
|
||||
body := jsonutils.NewDict()
|
||||
body.Add(usages, "usage")
|
||||
appsrv.SendJSON(w, body)
|
||||
}
|
||||
@@ -162,10 +162,14 @@ func (a *authManager) reAuth() {
|
||||
time.AfterFunc(time.Duration(duration.Nanoseconds()/2), a.reAuth)
|
||||
}
|
||||
|
||||
func (a *authManager) getServiceURL(service, region, zone, endpointType string) (string, error) {
|
||||
func (a *authManager) GetServiceURL(service, region, zone, endpointType string) (string, error) {
|
||||
return a.adminCredential.GetServiceURL(service, region, zone, endpointType)
|
||||
}
|
||||
|
||||
func (a *authManager) GetServiceURLs(service, region, zone, endpointType string) ([]string, error) {
|
||||
return a.adminCredential.GetServiceURLs(service, region, zone, endpointType)
|
||||
}
|
||||
|
||||
func (a *authManager) getTokenString() string {
|
||||
return a.adminCredential.GetTokenString()
|
||||
}
|
||||
@@ -203,7 +207,11 @@ func Verify(tokenId string) (mcclient.TokenCredential, error) {
|
||||
}
|
||||
|
||||
func GetServiceURL(service, region, zone, endpointType string) (string, error) {
|
||||
return manager.getServiceURL(service, region, zone, endpointType)
|
||||
return manager.GetServiceURL(service, region, zone, endpointType)
|
||||
}
|
||||
|
||||
func GetServiceURLs(service, region, zone, endpointType string) ([]string, error) {
|
||||
return manager.GetServiceURLs(service, region, zone, endpointType)
|
||||
}
|
||||
|
||||
func GetTokenString() string {
|
||||
|
||||
@@ -93,11 +93,11 @@ func joinUrl(baseUrl, path string) string {
|
||||
return fmt.Sprintf("%s%s", baseUrl, path)
|
||||
}
|
||||
|
||||
func (this *Client) rawRequest(ctx context.Context, endpoint string, token string, method string, url string, header http.Header, body io.Reader) (*http.Response, error) {
|
||||
func (this *Client) rawRequest(ctx context.Context, endpoint string, token string, method httputils.THttpMethod, url string, header http.Header, body io.Reader) (*http.Response, error) {
|
||||
return httputils.Request(this.httpconn, ctx, method, joinUrl(endpoint, url), getDefaultHeader(header, token), body, this.debug)
|
||||
}
|
||||
|
||||
func (this *Client) jsonRequest(ctx context.Context, endpoint string, token string, method string, url string, header http.Header, body jsonutils.JSONObject) (http.Header, jsonutils.JSONObject, error) {
|
||||
func (this *Client) jsonRequest(ctx context.Context, endpoint string, token string, method httputils.THttpMethod, url string, header http.Header, body jsonutils.JSONObject) (http.Header, jsonutils.JSONObject, error) {
|
||||
/*bodystr := ""
|
||||
if body != nil {
|
||||
bodystr = body.String()
|
||||
|
||||
@@ -67,7 +67,7 @@ func (this *BaseManager) versionedURL(path string) string {
|
||||
}
|
||||
|
||||
func (this *BaseManager) jsonRequest(session *mcclient.ClientSession,
|
||||
method string, path string,
|
||||
method httputils.THttpMethod, path string,
|
||||
header http.Header, body jsonutils.JSONObject) (http.Header, jsonutils.JSONObject, error) {
|
||||
return session.JSONVersionRequest(this.serviceType, this.endpointType,
|
||||
method, this.versionedURL(path),
|
||||
@@ -75,7 +75,7 @@ func (this *BaseManager) jsonRequest(session *mcclient.ClientSession,
|
||||
}
|
||||
|
||||
func (this *BaseManager) rawRequest(session *mcclient.ClientSession,
|
||||
method string, path string,
|
||||
method httputils.THttpMethod, path string,
|
||||
header http.Header, body io.Reader) (*http.Response, error) {
|
||||
return session.RawVersionRequest(this.serviceType, this.endpointType,
|
||||
method, this.versionedURL(path),
|
||||
@@ -148,7 +148,7 @@ func (this *BaseManager) _list(session *mcclient.ClientSession, path, responseKe
|
||||
return &ListResult{rets, int(total), int(limit), int(offset)}, nil
|
||||
}
|
||||
|
||||
func (this *BaseManager) _submit(session *mcclient.ClientSession, method string, path string, body jsonutils.JSONObject, respKey string) (jsonutils.JSONObject, error) {
|
||||
func (this *BaseManager) _submit(session *mcclient.ClientSession, method httputils.THttpMethod, path string, body jsonutils.JSONObject, respKey string) (jsonutils.JSONObject, error) {
|
||||
hdr, resp, e := this.jsonRequest(session, method, path, nil, body)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
@@ -211,7 +211,7 @@ func SubmitResults2ListResult(results []SubmitResult) *ListResult {
|
||||
return &ListResult{Data: arr, Total: len(arr), Limit: 0, Offset: 0}
|
||||
}
|
||||
|
||||
func (this *BaseManager) _batch(session *mcclient.ClientSession, method string, path string, ids []string, body jsonutils.JSONObject, respKey string) []SubmitResult {
|
||||
func (this *BaseManager) _batch(session *mcclient.ClientSession, method httputils.THttpMethod, path string, ids []string, body jsonutils.JSONObject, respKey string) []SubmitResult {
|
||||
return BatchDo(ids, func(id string) (jsonutils.JSONObject, error) {
|
||||
u := fmt.Sprintf(path, url.PathEscape(id))
|
||||
return this._submit(session, method, u, body, respKey)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/util/httputils"
|
||||
)
|
||||
|
||||
var RawResource *RawResourceManager
|
||||
@@ -49,7 +50,7 @@ type RawResourceManager struct {
|
||||
serviceType string
|
||||
}
|
||||
|
||||
func (m *RawResourceManager) request(s *mcclient.ClientSession, method string, path string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
func (m *RawResourceManager) request(s *mcclient.ClientSession, method httputils.THttpMethod, path string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
_, ret, err := s.JSONRequest(m.serviceType, "", method, path, nil, body)
|
||||
return ret, err
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ type ImageManager struct {
|
||||
const (
|
||||
IMAGE_META = "X-Image-Meta-"
|
||||
IMAGE_META_PROPERTY = "X-Image-Meta-Property-"
|
||||
|
||||
IMAGE_META_COPY_FROM = "x-glance-api-copy-from"
|
||||
)
|
||||
|
||||
func decodeMeta(str string) string {
|
||||
@@ -34,14 +36,22 @@ func decodeMeta(str string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func fetchImageMeta(h http.Header) jsonutils.JSONObject {
|
||||
func FetchImageMeta(h http.Header) jsonutils.JSONObject {
|
||||
meta := jsonutils.NewDict()
|
||||
meta.Add(jsonutils.NewDict(), "properties")
|
||||
for k, v := range h {
|
||||
if len(k) > len(IMAGE_META_PROPERTY) && k[:len(IMAGE_META_PROPERTY)] == IMAGE_META_PROPERTY {
|
||||
meta.Add(jsonutils.NewString(decodeMeta(v[0])), "properties", strings.ToLower(k[len(IMAGE_META_PROPERTY):]))
|
||||
} else if len(k) > len(IMAGE_META) && k[:len(IMAGE_META)] == IMAGE_META {
|
||||
meta.Add(jsonutils.NewString(decodeMeta(v[0])), strings.ToLower(k[len(IMAGE_META):]))
|
||||
if strings.HasPrefix(k, IMAGE_META_PROPERTY) {
|
||||
k := strings.ToLower(k[len(IMAGE_META_PROPERTY):])
|
||||
meta.Add(jsonutils.NewString(decodeMeta(v[0])), "properties", k)
|
||||
if strings.IndexByte(k, '-') > 0 {
|
||||
meta.Add(jsonutils.NewString(decodeMeta(v[0])), "properties", strings.Replace(k, "-", "_", -1))
|
||||
}
|
||||
} else if strings.HasPrefix(k, IMAGE_META) {
|
||||
k := strings.ToLower(k[len(IMAGE_META):])
|
||||
meta.Add(jsonutils.NewString(decodeMeta(v[0])), k)
|
||||
if strings.IndexByte(k, '-') > 0 {
|
||||
meta.Add(jsonutils.NewString(decodeMeta(v[0])), strings.Replace(k, "-", "_", -1))
|
||||
}
|
||||
}
|
||||
}
|
||||
return meta
|
||||
@@ -59,7 +69,7 @@ func (this *ImageManager) GetById(session *mcclient.ClientSession, id string, pa
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
return fetchImageMeta(h), nil
|
||||
return FetchImageMeta(h), nil
|
||||
}
|
||||
|
||||
func (this *ImageManager) GetByName(session *mcclient.ClientSession, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
@@ -445,7 +455,7 @@ func (this *ImageManager) _create(s *mcclient.ClientSession, params jsonutils.JS
|
||||
}
|
||||
imageId, _ := params.GetString("image_id")
|
||||
path := fmt.Sprintf("/%s", this.URLPath())
|
||||
method := "POST"
|
||||
method := httputils.POST
|
||||
if len(imageId) == 0 {
|
||||
osType, err := params.GetString("properties", "os_type")
|
||||
if err != nil {
|
||||
@@ -481,7 +491,7 @@ func (this *ImageManager) _create(s *mcclient.ClientSession, params jsonutils.JS
|
||||
}
|
||||
body = nil
|
||||
size = 0
|
||||
headers.Set("x-glance-api-copy-from", copyFromUrl)
|
||||
headers.Set(IMAGE_META_COPY_FROM, copyFromUrl)
|
||||
}
|
||||
headers.Set(fmt.Sprintf("%s%s", IMAGE_META, utils.Capitalize("container-format")), "bare")
|
||||
if body != nil {
|
||||
@@ -542,7 +552,7 @@ func (this *ImageManager) Download(s *mcclient.ClientSession, id string) (jsonut
|
||||
path := fmt.Sprintf("/%s/%s", this.URLPath(), url.PathEscape(id))
|
||||
resp, err := this.rawRequest(s, "GET", path, nil, nil)
|
||||
if err == nil && resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
return fetchImageMeta(resp.Header), resp.Body, nil
|
||||
return FetchImageMeta(resp.Header), resp.Body, nil
|
||||
} else {
|
||||
_, _, err = s.ParseJSONResponse(resp, err)
|
||||
return nil, nil, err
|
||||
@@ -560,7 +570,35 @@ func init() {
|
||||
"OS_Distribution", "OS_version",
|
||||
"Min_disk", "Min_ram", "Status",
|
||||
"Notes", "OS_arch", "Preference",
|
||||
"OS_Codename", "Parent_id", "Description"},
|
||||
"OS_Codename", "Description"},
|
||||
[]string{"Owner", "Owner_name"})}
|
||||
register(&Images)
|
||||
registerV2(&Images)
|
||||
}
|
||||
|
||||
type SImageUsageManager struct {
|
||||
ResourceManager
|
||||
}
|
||||
|
||||
func (this *SImageUsageManager) GetUsage(session *mcclient.ClientSession, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
url := "/usages"
|
||||
if params != nil {
|
||||
query := params.QueryString()
|
||||
if len(query) > 0 {
|
||||
url = fmt.Sprintf("%s?%s", url, query)
|
||||
}
|
||||
}
|
||||
return this._get(session, url, "usage")
|
||||
}
|
||||
|
||||
var (
|
||||
ImageUsages SImageUsageManager
|
||||
)
|
||||
|
||||
func init() {
|
||||
ImageUsages = SImageUsageManager{NewImageManager("image-usage", "image-usages",
|
||||
[]string{},
|
||||
[]string{})}
|
||||
|
||||
registerV2(&ImageUsages)
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func (this *QuotaManager) DoQuotaSet(s *mcclient.ClientSession, params jsonutils
|
||||
}
|
||||
data := quotas.Copy("tenant", "user")
|
||||
body := jsonutils.NewDict()
|
||||
body.Add(data, "quotas")
|
||||
body.Add(data, this.KeywordPlural)
|
||||
return this._post(s, url, body, this.KeywordPlural)
|
||||
}
|
||||
|
||||
@@ -51,12 +51,13 @@ func (this *QuotaManager) DoQuotaCheck(s *mcclient.ClientSession, params jsonuti
|
||||
}
|
||||
data := quotas.Copy("tenant", "user")
|
||||
body := jsonutils.NewDict()
|
||||
body.Add(data, "quotas")
|
||||
body.Add(data, this.KeywordPlural)
|
||||
return this._post(s, url, body, this.KeywordPlural)
|
||||
}
|
||||
|
||||
var (
|
||||
Quotas QuotaManager
|
||||
Quotas QuotaManager
|
||||
ImageQuotas QuotaManager
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -64,4 +65,9 @@ func init() {
|
||||
[]string{},
|
||||
[]string{})}
|
||||
registerCompute(&Quotas)
|
||||
|
||||
ImageQuotas = QuotaManager{NewImageManager("image-quota", "image-quotas",
|
||||
[]string{},
|
||||
[]string{})}
|
||||
registerV2(&ImageQuotas)
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ func _getModule(session *mcclient.ClientSession, name string) (BaseManagerInterf
|
||||
}
|
||||
mods, ok := modtable[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("No such module: %s", name)
|
||||
return nil, fmt.Errorf("No such module %s for version %s", name, session.GetApiVersion())
|
||||
}
|
||||
for _, mod := range mods {
|
||||
url, e := session.GetServiceURL(mod.ServiceType(), mod.EndpointType())
|
||||
|
||||
@@ -20,6 +20,10 @@ func register(mod BaseManagerInterface) {
|
||||
_register("v1", mod)
|
||||
}
|
||||
|
||||
func registerV2(mod BaseManagerInterface) {
|
||||
_register("v2", mod)
|
||||
}
|
||||
|
||||
func Register(mod BaseManagerInterface) {
|
||||
register(mod)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
MutilVersionService = []string{"compute"}
|
||||
MutilVersionService = []string{"compute", "image"}
|
||||
ApiVersionByModule = true
|
||||
)
|
||||
|
||||
@@ -157,7 +157,7 @@ func (this *ClientSession) getBaseUrl(service, endpointType, apiVersion string)
|
||||
}
|
||||
|
||||
func (this *ClientSession) RawVersionRequest(
|
||||
service, endpointType, method, url string,
|
||||
service, endpointType string, method httputils.THttpMethod, url string,
|
||||
headers http.Header, body io.Reader,
|
||||
apiVersion string,
|
||||
) (*http.Response, error) {
|
||||
@@ -179,12 +179,12 @@ func (this *ClientSession) RawVersionRequest(
|
||||
method, url, tmpHeader, body)
|
||||
}
|
||||
|
||||
func (this *ClientSession) RawRequest(service, endpointType, method, url string, headers http.Header, body io.Reader) (*http.Response, error) {
|
||||
func (this *ClientSession) RawRequest(service, endpointType string, method httputils.THttpMethod, url string, headers http.Header, body io.Reader) (*http.Response, error) {
|
||||
return this.RawVersionRequest(service, endpointType, method, url, headers, body, "")
|
||||
}
|
||||
|
||||
func (this *ClientSession) JSONVersionRequest(
|
||||
service, endpointType, method, url string,
|
||||
service, endpointType string, method httputils.THttpMethod, url string,
|
||||
headers http.Header, body jsonutils.JSONObject,
|
||||
apiVersion string,
|
||||
) (http.Header, jsonutils.JSONObject, error) {
|
||||
@@ -206,7 +206,7 @@ func (this *ClientSession) JSONVersionRequest(
|
||||
method, url, tmpHeader, body)
|
||||
}
|
||||
|
||||
func (this *ClientSession) JSONRequest(service, endpointType, method, url string, headers http.Header, body jsonutils.JSONObject) (http.Header, jsonutils.JSONObject, error) {
|
||||
func (this *ClientSession) JSONRequest(service, endpointType string, method httputils.THttpMethod, url string, headers http.Header, body jsonutils.JSONObject) (http.Header, jsonutils.JSONObject, error) {
|
||||
return this.JSONVersionRequest(service, endpointType, method, url, headers, body, "")
|
||||
}
|
||||
|
||||
|
||||
29
pkg/util/fileutils2/blkid.go
Normal file
29
pkg/util/fileutils2/blkid.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package fileutils2
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"os/exec"
|
||||
"yunion.io/x/log"
|
||||
)
|
||||
|
||||
const (
|
||||
blkidTypePattern = `TYPE="(?P<type>\w+)"`
|
||||
)
|
||||
|
||||
var (
|
||||
blkidTypeRegexp = regexp.MustCompile(blkidTypePattern)
|
||||
)
|
||||
|
||||
func GetBlkidType(filepath string) string {
|
||||
cmd := exec.Command("blkid", filepath)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Errorf("blkid fail %s %s", filepath, err)
|
||||
return ""
|
||||
}
|
||||
matches := blkidTypeRegexp.FindStringSubmatch(string(out))
|
||||
if len(matches) > 1 {
|
||||
return matches[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
27
pkg/util/fileutils2/blkid_test.go
Normal file
27
pkg/util/fileutils2/blkid_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package fileutils2
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGetBlkidType(t *testing.T) {
|
||||
cases := []struct {
|
||||
In string
|
||||
Want string
|
||||
}{
|
||||
{
|
||||
`/dev/sda2: UUID="87a523a8-b382-4b45-a291-7ae56a13c99a" TYPE="ext4" PARTLABEL="Linux" PARTUUID="d9ac3dd7-da80-4c57-a701-e37956c07687"`,
|
||||
"ext4",
|
||||
},
|
||||
{
|
||||
`/opt/isoimage/iso/yunion-20180622.iso: UUID="2018-06-22-23-04-12-00" LABEL="CDROM" TYPE="iso9660" PTTYPE="dos"`,
|
||||
"iso9660",
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
matches := blkidTypeRegexp.FindStringSubmatch(c.In)
|
||||
if len(matches) > 1 && matches[1] == c.Want {
|
||||
t.Logf("%s", matches)
|
||||
} else {
|
||||
t.Errorf("fail")
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user