yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/proxmox/proxmox.go (about) 1 // Copyright 2019 Yunion 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package proxmox 16 17 import ( 18 "context" 19 "crypto/tls" 20 "fmt" 21 "net/http" 22 "net/url" 23 "strings" 24 25 "yunion.io/x/jsonutils" 26 "yunion.io/x/log" 27 "yunion.io/x/pkg/errors" 28 29 api "yunion.io/x/cloudmux/pkg/apis/compute" 30 "yunion.io/x/cloudmux/pkg/cloudprovider" 31 "yunion.io/x/onecloud/pkg/util/httputils" 32 ) 33 34 const ( 35 CLOUD_PROVIDER_PROXMOX = api.CLOUD_PROVIDER_PROXMOX 36 AUTH_ADDR = "/access/ticket" 37 ) 38 39 type SProxmoxClient struct { 40 *ProxmoxClientConfig 41 } 42 43 type ProxmoxClientConfig struct { 44 cpcfg cloudprovider.ProviderConfig 45 username string 46 password string 47 host string 48 authURL string 49 port int 50 51 csrfToken string 52 authTicket string // Combination of user, realm, token ID and UUID 53 54 debug bool 55 } 56 57 func NewProxmoxClientConfig(username, password, host string, port int) *ProxmoxClientConfig { 58 59 cfg := &ProxmoxClientConfig{ 60 username: username, 61 password: password, 62 host: host, 63 authURL: fmt.Sprintf("https://%s:%d/api2/json", host, port), 64 port: port, 65 } 66 return cfg 67 } 68 69 func (self *ProxmoxClientConfig) Debug(debug bool) *ProxmoxClientConfig { 70 self.debug = debug 71 return self 72 } 73 74 func (self *ProxmoxClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *ProxmoxClientConfig { 75 self.cpcfg = cpcfg 76 return self 77 } 78 79 func NewProxmoxClient(cfg *ProxmoxClientConfig) (*SProxmoxClient, error) { 80 81 client := &SProxmoxClient{ 82 ProxmoxClientConfig: cfg, 83 } 84 85 return client, client.auth() 86 } 87 88 func (self *SProxmoxClient) auth() error { 89 params := map[string]interface{}{ 90 "username": self.username, 91 "password": self.password, 92 } 93 ret, err := self.__jsonRequest(httputils.POST, AUTH_ADDR, params) 94 if err != nil { 95 return errors.Wrapf(err, "post") 96 } 97 98 dat, err := ret.Get("data") 99 if err != nil { 100 return errors.Wrapf(err, "decode data") 101 } 102 103 if ticket, err := dat.GetString("ticket"); err != nil { 104 return errors.Wrapf(err, "get ticket") 105 } else { 106 self.authTicket = ticket 107 } 108 109 if token, err := dat.GetString("CSRFPreventionToken"); err != nil { 110 return errors.Wrapf(err, "get Token") 111 } else { 112 self.csrfToken = token 113 } 114 115 return nil 116 } 117 118 func (self *SProxmoxClient) GetRegion() *SRegion { 119 region := &SRegion{client: self} 120 return region 121 } 122 123 func (self *SProxmoxClient) GetRegions() ([]SRegion, error) { 124 ret := []SRegion{} 125 ret = append(ret, SRegion{client: self}) 126 return ret, nil 127 } 128 129 type ProxmoxError struct { 130 Message string 131 Code int 132 Params []string 133 } 134 135 func (self ProxmoxError) Error() string { 136 return fmt.Sprintf("[%d] %s with params %s", self.Code, self.Message, self.Params) 137 } 138 139 func (ce *ProxmoxError) ParseErrorFromJsonResponse(statusCode int, body jsonutils.JSONObject) error { 140 if body != nil { 141 body.Unmarshal(ce) 142 log.Errorf("error: %v", body.PrettyString()) 143 } 144 if ce.Code == 0 && statusCode > 0 { 145 ce.Code = statusCode 146 } 147 if ce.Code == 404 || ce.Code == 400 || ce.Code == 500 { 148 log.Errorf("code: %d", ce.Code) 149 return errors.Wrap(cloudprovider.ErrNotFound, ce.Error()) 150 } 151 return ce 152 } 153 154 func (cli *SProxmoxClient) getDefaultClient() *http.Client { 155 client := httputils.GetAdaptiveTimeoutClient() 156 httputils.SetClientProxyFunc(client, cli.cpcfg.ProxyFunc) 157 ts, _ := client.Transport.(*http.Transport) 158 ts.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} 159 client.Transport = cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response), error) { 160 if cli.cpcfg.ReadOnly { 161 if req.Method == "GET" || req.Method == "HEAD" { 162 return nil, nil 163 } 164 // 认证 165 if req.Method == "POST" && strings.HasSuffix(req.URL.Path, "/access/ticket") { 166 return nil, nil 167 } 168 return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path) 169 } 170 return nil, nil 171 }) 172 return client 173 } 174 175 func (cli *SProxmoxClient) post(res string, params interface{}) (jsonutils.JSONObject, error) { 176 resp, err := cli._jsonRequest(httputils.POST, res, params) 177 if err != nil { 178 return resp, err 179 } 180 taskId, err := resp.GetString("data") 181 if err != nil { 182 return resp, err 183 } 184 _, err = cli.waitTask(taskId) 185 186 return resp, err 187 } 188 189 func (cli *SProxmoxClient) put(res string, params url.Values, body jsonutils.JSONObject, retVal interface{}) error { 190 if params != nil { 191 res = fmt.Sprintf("%s?%s", res, params.Encode()) 192 } 193 resp, err := cli._jsonRequest(httputils.PUT, res, body) 194 if err != nil { 195 return err 196 } 197 taskId, err := resp.GetString("data") 198 if err != nil { 199 return err 200 } 201 _, err = cli.waitTask(taskId) 202 203 return err 204 } 205 206 func (cli *SProxmoxClient) get(res string, params url.Values, retVal interface{}) error { 207 resp, err := cli._jsonRequest(httputils.GET, res, nil) 208 if err != nil { 209 return err 210 } 211 dat, err := resp.Get("data") 212 if err != nil { 213 return errors.Wrapf(err, "decode data") 214 } 215 216 return dat.Unmarshal(retVal) 217 } 218 219 func (cli *SProxmoxClient) getAgent(res string, params url.Values, retVal interface{}) error { 220 resp, err := cli._jsonRequest(httputils.GET, res, nil) 221 if err != nil { 222 return err 223 } 224 dat, err := resp.Get("data") 225 if err != nil { 226 return errors.Wrapf(err, "decode data") 227 } 228 ret, err := dat.Get("result") 229 if err != nil { 230 return errors.Wrapf(err, "decode data") 231 } 232 233 return ret.Unmarshal(retVal) 234 } 235 236 func (cli *SProxmoxClient) del(res string, params url.Values, retVal interface{}) error { 237 if params != nil { 238 res = fmt.Sprintf("%s?%s", res, params.Encode()) 239 } 240 resp, err := cli._jsonRequest(httputils.DELETE, res, nil) 241 if err != nil { 242 return err 243 } 244 taskId, err := resp.GetString("data") 245 if err != nil { 246 return err 247 } 248 _, err = cli.waitTask(taskId) 249 250 return err 251 252 } 253 254 func (cli *SProxmoxClient) _jsonRequest(method httputils.THttpMethod, res string, params interface{}) (jsonutils.JSONObject, error) { 255 ret, err := cli.__jsonRequest(method, res, params) 256 if err != nil { 257 if e, ok := err.(*ProxmoxError); ok && e.Code == 401 { 258 cli.auth() 259 return cli.__jsonRequest(method, res, params) 260 } 261 return ret, err 262 } 263 return ret, nil 264 } 265 266 func (cli *SProxmoxClient) __jsonRequest(method httputils.THttpMethod, res string, params interface{}) (jsonutils.JSONObject, error) { 267 client := httputils.NewJsonClient(cli.getDefaultClient()) 268 url := fmt.Sprintf("%s/%s", cli.authURL, strings.TrimPrefix(res, "/")) 269 req := httputils.NewJsonRequest(method, url, params) 270 header := http.Header{} 271 if len(cli.csrfToken) > 0 && len(cli.csrfToken) > 0 && res != AUTH_ADDR { 272 header.Set("Cookie", "PVEAuthCookie="+cli.authTicket) 273 header.Set("CSRFPreventionToken", cli.csrfToken) 274 } 275 276 //header.Set("Content-Type", "application/x-www-form-urlencoded") 277 //header.Set("Accept", "application/json") 278 279 req.SetHeader(header) 280 oe := &ProxmoxError{} 281 _, resp, err := client.Send(context.Background(), req, oe, cli.debug) 282 if err != nil { 283 return nil, err 284 } 285 286 return resp, nil 287 } 288 289 func (self *SProxmoxClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) { 290 subAccount := cloudprovider.SSubAccount{} 291 subAccount.Name = self.cpcfg.Name 292 subAccount.Account = self.username 293 subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL 294 return []cloudprovider.SSubAccount{subAccount}, nil 295 } 296 297 func (self *SProxmoxClient) GetAccountId() string { 298 return self.host 299 } 300 301 func (self *SProxmoxClient) GetIRegions() []cloudprovider.ICloudRegion { 302 ret := []cloudprovider.ICloudRegion{} 303 region := self.GetRegion() 304 ret = append(ret, region) 305 return ret 306 } 307 308 func (self *SProxmoxClient) GetCapabilities() []string { 309 ret := []string{ 310 cloudprovider.CLOUD_CAPABILITY_COMPUTE, 311 cloudprovider.CLOUD_CAPABILITY_NETWORK + cloudprovider.READ_ONLY_SUFFIX, 312 } 313 return ret 314 }