| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- // Copyright 2019 Yunion
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package proxmox
- import (
- "bytes"
- "context"
- "crypto/tls"
- "fmt"
- "io"
- "mime/multipart"
- "net/http"
- "net/url"
- "path/filepath"
- "strings"
- "time"
- "yunion.io/x/jsonutils"
- "yunion.io/x/log"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/util/httputils"
- api "yunion.io/x/cloudmux/pkg/apis/compute"
- "yunion.io/x/cloudmux/pkg/cloudprovider"
- )
- const (
- CLOUD_PROVIDER_PROXMOX = api.CLOUD_PROVIDER_PROXMOX
- AUTH_ADDR = "/access/ticket"
- )
- type SProxmoxClient struct {
- *ProxmoxClientConfig
- }
- type ProxmoxClientConfig struct {
- cpcfg cloudprovider.ProviderConfig
- username string
- password string
- host string
- authURL string
- port int
- csrfToken string
- authTicket string // Combination of user, realm, token ID and UUID
- debug bool
- }
- func NewProxmoxClientConfig(username, password, host string, port int) *ProxmoxClientConfig {
- cfg := &ProxmoxClientConfig{
- username: username,
- password: password,
- host: host,
- authURL: fmt.Sprintf("https://%s:%d/api2/json", host, port),
- port: port,
- }
- return cfg
- }
- func (self *ProxmoxClientConfig) Debug(debug bool) *ProxmoxClientConfig {
- self.debug = debug
- return self
- }
- func (self *ProxmoxClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *ProxmoxClientConfig {
- self.cpcfg = cpcfg
- return self
- }
- func NewProxmoxClient(cfg *ProxmoxClientConfig) (*SProxmoxClient, error) {
- client := &SProxmoxClient{
- ProxmoxClientConfig: cfg,
- }
- return client, client.auth()
- }
- func (self *SProxmoxClient) auth() error {
- params := map[string]interface{}{
- "username": self.username,
- "password": self.password,
- }
- ret, err := self.__jsonRequest(httputils.POST, AUTH_ADDR, params)
- if err != nil {
- return errors.Wrapf(err, "post")
- }
- dat, err := ret.Get("data")
- if err != nil {
- return errors.Wrapf(err, "decode data")
- }
- if ticket, err := dat.GetString("ticket"); err != nil {
- return errors.Wrapf(err, "get ticket")
- } else {
- self.authTicket = ticket
- }
- if token, err := dat.GetString("CSRFPreventionToken"); err != nil {
- return errors.Wrapf(err, "get Token")
- } else {
- self.csrfToken = token
- }
- return nil
- }
- func (self *SProxmoxClient) GetRegion() *SRegion {
- region := &SRegion{client: self}
- return region
- }
- func (self *SProxmoxClient) GetRegions() ([]SRegion, error) {
- ret := []SRegion{}
- ret = append(ret, SRegion{client: self})
- return ret, nil
- }
- type ProxmoxError struct {
- Url string
- Message string
- Code int
- Params []string
- Errors string
- Status string
- }
- func (self ProxmoxError) Error() string {
- return jsonutils.Marshal(self).String()
- }
- func (ce *ProxmoxError) ParseErrorFromJsonResponse(statusCode int, status string, body jsonutils.JSONObject) error {
- ce.Status = status
- if body != nil {
- body.Unmarshal(ce)
- log.Errorf("%s status: %s(%d) error: %v", ce.Url, status, statusCode, body.PrettyString())
- }
- if ce.Code == 0 && statusCode > 0 {
- ce.Code = statusCode
- }
- if ce.Code == 404 {
- return errors.Wrap(cloudprovider.ErrNotFound, ce.Error())
- }
- return ce
- }
- func (cli *SProxmoxClient) getDefaultClient() *http.Client {
- client := httputils.GetAdaptiveTimeoutClient()
- httputils.SetClientProxyFunc(client, cli.cpcfg.ProxyFunc)
- ts, _ := client.Transport.(*http.Transport)
- ts.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
- client.Transport = cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response) error, error) {
- if cli.cpcfg.ReadOnly {
- if req.Method == "GET" || req.Method == "HEAD" {
- return nil, nil
- }
- // 认证
- if req.Method == "POST" && strings.HasSuffix(req.URL.Path, "/access/ticket") {
- return nil, nil
- }
- return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path)
- }
- return nil, nil
- })
- return client
- }
- func (cli *SProxmoxClient) post(res string, params interface{}) (jsonutils.JSONObject, error) {
- resp, err := cli._jsonRequest(httputils.POST, res, params)
- if err != nil {
- return resp, err
- }
- ret := struct {
- TaskId string
- }{}
- resp.Unmarshal(&ret)
- if len(ret.TaskId) > 0 && ret.TaskId != "null" {
- _, err = cli.waitTask(ret.TaskId)
- return resp, err
- }
- return resp, nil
- }
- func (cli *SProxmoxClient) put(res string, params url.Values, body jsonutils.JSONObject) error {
- if params != nil {
- res = fmt.Sprintf("%s?%s", res, params.Encode())
- }
- resp, err := cli._jsonRequest(httputils.PUT, res, body)
- if err != nil {
- return err
- }
- ret := struct {
- TaskId string
- }{}
- resp.Unmarshal(&ret)
- if len(ret.TaskId) > 0 && ret.TaskId != "null" {
- _, err = cli.waitTask(ret.TaskId)
- return err
- }
- return nil
- }
- func (cli *SProxmoxClient) get(res string, params url.Values, retVal interface{}) error {
- if len(params) > 0 {
- res = fmt.Sprintf("%s?%s", res, params.Encode())
- }
- resp, err := cli._jsonRequest(httputils.GET, res, nil)
- if err != nil {
- return err
- }
- dat, err := resp.Get("data")
- if err != nil {
- return errors.Wrapf(err, "decode data")
- }
- return dat.Unmarshal(retVal)
- }
- func (cli *SProxmoxClient) getAgent(res string, params url.Values, retVal interface{}) error {
- resp, err := cli._jsonRequest(httputils.GET, res, nil)
- if err != nil {
- return err
- }
- dat, err := resp.Get("data")
- if err != nil {
- return errors.Wrapf(err, "decode data")
- }
- ret, err := dat.Get("result")
- if err != nil {
- return errors.Wrapf(err, "decode data")
- }
- return ret.Unmarshal(retVal)
- }
- func (cli *SProxmoxClient) del(res string, params url.Values, retVal interface{}) error {
- if params != nil {
- res = fmt.Sprintf("%s?%s", res, params.Encode())
- }
- resp, err := cli._jsonRequest(httputils.DELETE, res, nil)
- if err != nil {
- return err
- }
- taskId, err := resp.GetString("data")
- if err != nil {
- return err
- }
- _, err = cli.waitTask(taskId)
- return err
- }
- func (cli *SProxmoxClient) _jsonRequest(method httputils.THttpMethod, res string, params interface{}) (jsonutils.JSONObject, error) {
- ret, err := cli.__jsonRequest(method, res, params)
- if err != nil {
- if e, ok := err.(*ProxmoxError); ok && e.Code == 401 {
- cli.auth()
- return cli.__jsonRequest(method, res, params)
- }
- return ret, err
- }
- return ret, nil
- }
- func (cli *SProxmoxClient) __jsonRequest(method httputils.THttpMethod, res string, params interface{}) (jsonutils.JSONObject, error) {
- client := httputils.NewJsonClient(cli.getDefaultClient())
- url := fmt.Sprintf("%s/%s", cli.authURL, strings.TrimPrefix(res, "/"))
- req := httputils.NewJsonRequest(method, url, params)
- header := http.Header{}
- if len(cli.csrfToken) > 0 && len(cli.csrfToken) > 0 && res != AUTH_ADDR {
- header.Set("Cookie", "PVEAuthCookie="+cli.authTicket)
- header.Set("CSRFPreventionToken", cli.csrfToken)
- }
- req.SetHeader(header)
- oe := &ProxmoxError{Url: url}
- _, resp, err := client.Send(context.Background(), req, oe, cli.debug)
- if err != nil {
- return nil, err
- }
- return resp, nil
- }
- func (cli *SProxmoxClient) upload(node, storageName, filename string, reader io.Reader) (*SImage, error) {
- if !strings.HasSuffix(filename, ".iso") {
- filename = filename + ".iso"
- }
- filename = filepath.Base(filename)
- client := cli.getDefaultClient()
- res := fmt.Sprintf("/nodes/%s/storage/%s/upload", node, storageName)
- url := fmt.Sprintf("%s/%s", cli.authURL, strings.TrimPrefix(res, "/"))
- body := &bytes.Buffer{}
- writer := multipart.NewWriter(body)
- err := writer.WriteField("content", "iso")
- if err != nil {
- return nil, err
- }
- part, err := writer.CreateFormFile("filename", filename)
- if err != nil {
- return nil, err
- }
- _, err = io.Copy(part, reader)
- if err != nil {
- return nil, errors.Wrapf(err, "io.Copy")
- }
- writer.Close()
- req, err := http.NewRequest("POST", url, body)
- if err != nil {
- return nil, err
- }
- req.Header.Set("Content-Type", writer.FormDataContentType())
- if len(cli.csrfToken) > 0 && len(cli.csrfToken) > 0 && res != AUTH_ADDR {
- req.Header.Set("Cookie", "PVEAuthCookie="+cli.authTicket)
- req.Header.Set("CSRFPreventionToken", cli.csrfToken)
- }
- resp, err := client.Do(req)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- data, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, err
- }
- obj, err := jsonutils.Parse(data)
- if err != nil {
- return nil, err
- }
- if obj.Contains("errors") {
- return nil, fmt.Errorf("%s", string(data))
- }
- now := time.Now()
- for now.Sub(time.Now()) < time.Minute*1 {
- images, err := cli.GetImages(node, storageName)
- if err != nil {
- return nil, errors.Wrapf(err, "GetImageStatus")
- }
- for i := range images {
- if strings.HasSuffix(images[i].Volid, filename) {
- return &images[i], nil
- }
- }
- time.Sleep(time.Second * 10)
- }
- return nil, errors.Wrapf(cloudprovider.ErrNotFound, "after upload")
- }
- func (cli *SProxmoxClient) GetCloudRegionExternalIdPrefix() string {
- return fmt.Sprintf("%s/%s", CLOUD_PROVIDER_PROXMOX, cli.cpcfg.Id)
- }
- func (self *SProxmoxClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
- subAccount := cloudprovider.SSubAccount{}
- subAccount.Id = self.host
- subAccount.Name = self.cpcfg.Name
- subAccount.Account = self.username
- subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
- return []cloudprovider.SSubAccount{subAccount}, nil
- }
- func (self *SProxmoxClient) GetAccountId() string {
- return self.host
- }
- func (self *SProxmoxClient) GetIRegions() ([]cloudprovider.ICloudRegion, error) {
- ret := []cloudprovider.ICloudRegion{}
- region := self.GetRegion()
- ret = append(ret, region)
- return ret, nil
- }
- func (self *SProxmoxClient) GetCapabilities() []string {
- ret := []string{
- cloudprovider.CLOUD_CAPABILITY_COMPUTE,
- cloudprovider.CLOUD_CAPABILITY_NETWORK + cloudprovider.READ_ONLY_SUFFIX,
- }
- return ret
- }
|