yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/nutanix/nutanix.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 nutanix 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "net/http" 22 "net/url" 23 "time" 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 NUTANIX_VERSION_V2 = "v2.0" 36 NUTANIX_VERSION_V0_8 = "v0.8" 37 NUTANIX_VERSION_V3 = "v3" 38 39 CLOUD_PROVIDER_NUTANIX = api.CLOUD_PROVIDER_NUTANIX 40 ) 41 42 type NutanixClientConfig struct { 43 cpcfg cloudprovider.ProviderConfig 44 username string 45 password string 46 host string 47 port int 48 debug bool 49 } 50 51 func NewNutanixClientConfig(host, username, password string, port int) *NutanixClientConfig { 52 cfg := &NutanixClientConfig{ 53 host: host, 54 username: username, 55 password: password, 56 port: port, 57 } 58 return cfg 59 } 60 61 func (cfg *NutanixClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *NutanixClientConfig { 62 cfg.cpcfg = cpcfg 63 return cfg 64 } 65 66 func (cfg *NutanixClientConfig) Debug(debug bool) *NutanixClientConfig { 67 cfg.debug = debug 68 return cfg 69 } 70 71 func (cfg NutanixClientConfig) Copy() NutanixClientConfig { 72 return cfg 73 } 74 75 type SNutanixClient struct { 76 *NutanixClientConfig 77 } 78 79 func NewNutanixClient(cfg *NutanixClientConfig) (*SNutanixClient, error) { 80 client := &SNutanixClient{ 81 NutanixClientConfig: cfg, 82 } 83 return client, client.auth() 84 } 85 86 func (self *SNutanixClient) GetRegion() (*SRegion, error) { 87 return &SRegion{cli: self}, nil 88 } 89 90 func (self *SNutanixClient) GetAccountId() string { 91 return self.host 92 } 93 94 func (self *SNutanixClient) GetCapabilities() []string { 95 return []string{ 96 cloudprovider.CLOUD_CAPABILITY_COMPUTE, 97 cloudprovider.CLOUD_CAPABILITY_NETWORK, 98 } 99 } 100 101 func (self *SNutanixClient) auth() error { 102 _, err := self.list("clusters", nil, nil) 103 return err 104 } 105 106 func (self *SNutanixClient) _getBaseDomain(version string) string { 107 if len(version) == 0 { 108 version = NUTANIX_VERSION_V2 109 } 110 return fmt.Sprintf("https://%s:%d/api/nutanix/%s", self.host, self.port, version) 111 } 112 113 func (self *SNutanixClient) getBaseDomain() string { 114 return self._getBaseDomain("") 115 } 116 117 func (self *SNutanixClient) getBaseDomainV0_8() string { 118 return self._getBaseDomain(NUTANIX_VERSION_V0_8) 119 } 120 121 func (cli *SNutanixClient) getDefaultClient(timeout time.Duration) *http.Client { 122 client := httputils.GetDefaultClient() 123 if timeout > 0 { 124 client = httputils.GetTimeoutClient(timeout) 125 } 126 proxy := func(req *http.Request) (*url.URL, error) { 127 req.SetBasicAuth(cli.username, cli.password) 128 if cli.cpcfg.ProxyFunc != nil { 129 cli.cpcfg.ProxyFunc(req) 130 } 131 return nil, nil 132 } 133 httputils.SetClientProxyFunc(client, proxy) 134 135 ts, _ := client.Transport.(*http.Transport) 136 client.Transport = cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response), error) { 137 if cli.cpcfg.ReadOnly { 138 if req.Method == "GET" { 139 return nil, nil 140 } 141 return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path) 142 } 143 return nil, nil 144 }) 145 146 return client 147 } 148 149 func (self *SNutanixClient) _list(res string, params url.Values) (jsonutils.JSONObject, error) { 150 url := fmt.Sprintf("%s/%s", self.getBaseDomain(), res) 151 if len(params) > 0 { 152 url = fmt.Sprintf("%s?%s", url, params.Encode()) 153 } 154 return self.jsonRequest(httputils.GET, url, nil) 155 } 156 157 func (self *SNutanixClient) _post(res string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) { 158 url := fmt.Sprintf("%s/%s", self.getBaseDomain(), res) 159 if body == nil { 160 body = jsonutils.NewDict() 161 } 162 return self.jsonRequest(httputils.POST, url, body) 163 } 164 165 func (self *SNutanixClient) _update(res, id string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) { 166 url := fmt.Sprintf("%s/%s/%s", self.getBaseDomain(), res, id) 167 if body == nil { 168 body = jsonutils.NewDict() 169 } 170 return self.jsonRequest(httputils.PUT, url, body) 171 } 172 173 func (self *SNutanixClient) _upload(res, id string, header http.Header, body io.Reader) (jsonutils.JSONObject, error) { 174 url := fmt.Sprintf("%s/%s/%s", self.getBaseDomainV0_8(), res, id) 175 return self.rawRequest(httputils.PUT, url, header, body) 176 } 177 178 func (self *SNutanixClient) upload(res, id string, header http.Header, body io.Reader) (jsonutils.JSONObject, error) { 179 return self._upload(res, id, header, body) 180 } 181 182 func (self *SNutanixClient) _delete(res, id string) (jsonutils.JSONObject, error) { 183 url := fmt.Sprintf("%s/%s/%s", self.getBaseDomain(), res, id) 184 return self.jsonRequest(httputils.DELETE, url, nil) 185 } 186 187 func (self *SNutanixClient) list(res string, params url.Values, retVal interface{}) (int, error) { 188 resp, err := self._list(res, params) 189 if err != nil { 190 return 0, errors.Wrapf(err, "get %s", res) 191 } 192 if retVal != nil { 193 err = resp.Unmarshal(retVal, "entities") 194 if err != nil { 195 return 0, errors.Wrapf(err, "resp.Unmarshal") 196 } 197 } 198 total, err := resp.Int("metadata", "total_entities") 199 if err != nil { 200 return 0, errors.Wrapf(err, "get metadata total_entities") 201 } 202 return int(total), nil 203 } 204 205 func (self *SNutanixClient) delete(res, id string) error { 206 resp, err := self._delete(res, id) 207 if err != nil { 208 return errors.Wrapf(err, "delete %s", res) 209 } 210 if resp != nil && resp.Contains("task_uuid") { 211 task := struct { 212 TaskUUID string 213 }{} 214 resp.Unmarshal(&task) 215 if len(task.TaskUUID) > 0 { 216 _, err = self.wait(task.TaskUUID) 217 if err != nil { 218 return err 219 } 220 } 221 } 222 return nil 223 } 224 225 func (self *SNutanixClient) wait(taskId string) (string, error) { 226 resId := "" 227 err := cloudprovider.Wait(time.Second*5, time.Minute*10, func() (bool, error) { 228 task, err := self.getTask(taskId) 229 if err != nil { 230 return false, err 231 } 232 for _, entity := range task.EntityList { 233 if len(entity.EntityID) > 0 { 234 resId = entity.EntityID 235 } 236 } 237 log.Debugf("task %s %s status: %s", task.OperationType, task.UUID, task.ProgressStatus) 238 if task.ProgressStatus == "Succeeded" { 239 return true, nil 240 } 241 if task.ProgressStatus == "Failed" { 242 return false, errors.Errorf(jsonutils.Marshal(task.MetaResponse).String()) 243 } 244 return false, nil 245 }) 246 return resId, errors.Wrapf(err, "wait task %s", taskId) 247 } 248 249 func (self *SNutanixClient) update(res, id string, body jsonutils.JSONObject, retVal interface{}) error { 250 resp, err := self._update(res, id, body) 251 if err != nil { 252 return errors.Wrapf(err, "update %s/%s %v", res, id, body) 253 } 254 task := struct { 255 TaskUUID string 256 }{} 257 resp.Unmarshal(&task) 258 if len(task.TaskUUID) > 0 { 259 _, err = self.wait(task.TaskUUID) 260 if err != nil { 261 return err 262 } 263 } 264 if retVal != nil { 265 return resp.Unmarshal(retVal) 266 } 267 return nil 268 } 269 270 func (self *SNutanixClient) post(res string, body jsonutils.JSONObject, retVal interface{}) error { 271 resp, err := self._post(res, body) 272 if err != nil { 273 return errors.Wrapf(err, "post %s %v", res, body) 274 } 275 if retVal != nil { 276 if resp.Contains("entities") { 277 err = resp.Unmarshal(retVal, "entities") 278 } else { 279 err = resp.Unmarshal(retVal) 280 } 281 return err 282 } 283 return nil 284 } 285 286 func (self *SNutanixClient) listAll(res string, params url.Values, retVal interface{}) error { 287 if len(params) == 0 { 288 params = url.Values{} 289 } 290 entities := []jsonutils.JSONObject{} 291 page, count := 1, 1024 292 for { 293 params.Set("count", fmt.Sprintf("%d", count)) 294 params.Set("page", fmt.Sprintf("%d", page)) 295 resp, err := self._list(res, params) 296 if err != nil { 297 return errors.Wrapf(err, "list %s", res) 298 } 299 _entities, err := resp.GetArray("entities") 300 if err != nil { 301 return errors.Wrapf(err, "resp get entities") 302 } 303 entities = append(entities, _entities...) 304 totalEntities, err := resp.Int("metadata", "total_entities") 305 if err != nil { 306 return errors.Wrapf(err, "get resp total_entities") 307 } 308 if int64(page*count) >= totalEntities { 309 break 310 } 311 page++ 312 } 313 return jsonutils.Update(retVal, entities) 314 } 315 316 func (self *SNutanixClient) get(res string, id string, params url.Values, retVal interface{}) error { 317 url := fmt.Sprintf("%s/%s/%s", self.getBaseDomain(), res, id) 318 if len(params) > 0 { 319 url = fmt.Sprintf("%s?%s", url, params.Encode()) 320 } 321 resp, err := self.jsonRequest(httputils.GET, url, nil) 322 if err != nil { 323 return errors.Wrapf(err, "get %s/%s", res, id) 324 } 325 if retVal != nil { 326 return resp.Unmarshal(retVal) 327 } 328 return nil 329 } 330 331 func (self *SNutanixClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) { 332 subAccount := cloudprovider.SSubAccount{ 333 Account: self.username, 334 Name: self.cpcfg.Name, 335 HealthStatus: api.CLOUD_PROVIDER_HEALTH_NORMAL, 336 } 337 return []cloudprovider.SSubAccount{subAccount}, nil 338 } 339 340 func (self *SNutanixClient) jsonRequest(method httputils.THttpMethod, url string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) { 341 client := self.getDefaultClient(time.Duration(0)) 342 return _jsonRequest(client, method, url, nil, body, self.debug) 343 } 344 345 type sNutanixError struct { 346 DetailedMessage string 347 Message string 348 ErrorCode struct { 349 Code int 350 HelpUrl string 351 } 352 } 353 354 func (self *sNutanixError) Error() string { 355 return jsonutils.Marshal(self).String() 356 } 357 358 func (self *sNutanixError) ParseErrorFromJsonResponse(statusCode int, body jsonutils.JSONObject) error { 359 if body != nil { 360 body.Unmarshal(self) 361 } 362 if self.ErrorCode.Code == 1202 { 363 return errors.Wrapf(cloudprovider.ErrNotFound, self.Error()) 364 } 365 return self 366 } 367 368 func _jsonRequest(cli *http.Client, method httputils.THttpMethod, url string, header http.Header, body jsonutils.JSONObject, debug bool) (jsonutils.JSONObject, error) { 369 client := httputils.NewJsonClient(cli) 370 req := httputils.NewJsonRequest(method, url, body) 371 ne := &sNutanixError{} 372 _, resp, err := client.Send(context.Background(), req, ne, debug) 373 return resp, err 374 } 375 376 func (self *SNutanixClient) rawRequest(method httputils.THttpMethod, url string, header http.Header, body io.Reader) (jsonutils.JSONObject, error) { 377 client := self.getDefaultClient(time.Hour * 5) 378 _resp, err := _rawRequest(client, method, url, header, body, false) 379 _, resp, err := httputils.ParseJSONResponse("", _resp, err, self.debug) 380 return resp, err 381 } 382 383 func _rawRequest(cli *http.Client, method httputils.THttpMethod, url string, header http.Header, body io.Reader, debug bool) (*http.Response, error) { 384 return httputils.Request(cli, context.Background(), method, url, header, body, debug) 385 } 386 387 func (self *SNutanixClient) GetIRegions() []cloudprovider.ICloudRegion { 388 region := &SRegion{cli: self} 389 return []cloudprovider.ICloudRegion{region} 390 } 391 392 func (self *SNutanixClient) getTask(id string) (*STask, error) { 393 task := &STask{} 394 return task, self.get("tasks", id, nil, task) 395 }