diff --git a/examples/cmd/virtual_iter.go b/examples/cmd/virtual_iter.go new file mode 100644 index 0000000..dea05da --- /dev/null +++ b/examples/cmd/virtual_iter.go @@ -0,0 +1,61 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/softlayer/softlayer-go/filter" + "github.com/softlayer/softlayer-go/services" + "github.com/softlayer/softlayer-go/session" +) + +func init() { + rootCmd.AddCommand(listVirtCmd) +} + +var listVirtCmd = &cobra.Command{ + Use: "virt-list", + Short: "Lists all VSI on the account", + Long: `Lists all VSI on the account using an iterative aproach.`, + RunE: func(cmd *cobra.Command, args []string) error { + return RunListVirtCmd(cmd, args) + }, +} + +func RunListVirtCmd(cmd *cobra.Command, args []string) error { + + objectMask := "mask[id,hostname,domain,primaryIpAddress,primaryBackendIpAddress]" + // When using a result Limit to break up your API request, its important to include an orderBy objectFilter + // to enforce an order on the query, as the database might not always return results in the same order between + // queries otherwise + filters := filter.New() + filters = append(filters, filter.Path("virtualGuests.id").OrderBy("ASC")) + objectFilter := filters.Build() + // Sets up the session with authentication headers. + sess := session.New() + // uncomment to output API calls as they are made. + sess.Debug = true + + // creates a reference to the service object (SoftLayer_Account) + service := services.GetAccountService(sess) + + // Sets the mask, filter, result limit, and then makes the API call SoftLayer_Account::getHardware() + servers, err := service.Mask(objectMask).Filter(objectFilter).GetVirtualGuestsIter() + if err != nil { + return err + } + fmt.Printf("Id, Hostname, Domain, IP Address\n") + + for _, server := range servers { + ipAddress := "-" + // Servers with a private only connection will not have a primary IP + if server.PrimaryIpAddress != nil { + ipAddress = *server.PrimaryIpAddress + } + fmt.Printf("%v, %v, %v, %v\n", *server.Id, *server.Hostname, *server.Domain, ipAddress) + } + + + return nil +} diff --git a/services/account.go b/services/account.go index d962b41..7e5641b 100644 --- a/services/account.go +++ b/services/account.go @@ -16,6 +16,7 @@ package services import ( "fmt" "strings" + "sync" "github.com/softlayer/softlayer-go/datatypes" "github.com/softlayer/softlayer-go/session" @@ -1897,6 +1898,46 @@ func (r Account) GetVirtualGuests() (resp []datatypes.Virtual_Guest, err error) return } +// Retrieve An account's associated virtual guest objects in pages +func (r Account) GetVirtualGuestsIter() (resp []datatypes.Virtual_Guest, err error) { + + limit := 2 + // r.Session.Debug = true + if r.Options.Limit == nil { + r = r.Limit(limit) + } + // Get the first result set to find out how many total results we have to get through. + err = r.Session.DoRequest("SoftLayer_Account", "getVirtualGuests", nil, &r.Options, &resp) + if err != nil { + return + } + // how many api calls we have to make still. + apicalls := r.Options.GetRemainingAPICalls() + fmt.Printf(" (%v -%v) / %v = %v \n", r.Options.TotalItems, limit, limit, apicalls) + // First call returned all items (or none) and we are done. + if apicalls < 1 { + return + } + var wg sync.WaitGroup + for x := 1; x <= apicalls; x++ { + wg.Add(1) + + go func(i int) { + defer wg.Done() + offset := i * limit + this_resp := []datatypes.Virtual_Guest{} + // Makes a copy of the options, because doing a go routine will have &r.Optoins all be the same. + options := r.Options + options.Offset = &offset + err = r.Session.DoRequest("SoftLayer_Account", "getVirtualGuests", nil, &options, &this_resp) + resp = append(resp, this_resp...) + }(x) + } + wg.Wait() + return + +} + // Retrieve An account's associated virtual guest objects currently over bandwidth allocation. func (r Account) GetVirtualGuestsOverBandwidthAllocation() (resp []datatypes.Virtual_Guest, err error) { err = r.Session.DoRequest("SoftLayer_Account", "getVirtualGuestsOverBandwidthAllocation", nil, &r.Options, &resp) diff --git a/session/rest.go b/session/rest.go index 856a475..a269f7b 100644 --- a/session/rest.go +++ b/session/rest.go @@ -283,9 +283,17 @@ func makeHTTPRequest( defer resp.Body.Close() responseBody, err := ioutil.ReadAll(resp.Body) + if err != nil { return nil, resp.StatusCode, err } + if resp.Header["Softlayer-Total-Items"] != nil && len(resp.Header["Softlayer-Total-Items"]) == 1 { + var str_err error + options.TotalItems, str_err = strconv.Atoi(resp.Header["Softlayer-Total-Items"][0]) + if str_err != nil { + log.Println("[Error] Unable to convert Softlayer-Total-Items to int: ", str_err) + } + } if session.Debug { log.Println("[DEBUG] Status Code: ", resp.StatusCode) diff --git a/session/session.go b/session/session.go index 6b3697c..49e43de 100644 --- a/session/session.go +++ b/session/session.go @@ -262,7 +262,11 @@ func (r *Session) DoRequest(service string, method string, args []interface{}, o r.TransportHandler = getDefaultTransport(r.Endpoint) } - return r.TransportHandler.DoRequest(r, service, method, args, options, pResult) + err := r.TransportHandler.DoRequest(r, service, method, args, options, pResult) + if err != nil { + return err + } + return err } // SetTimeout creates a copy of the session and sets the passed timeout into it diff --git a/sl/options.go b/sl/options.go index 5d1dd0a..394fb8e 100644 --- a/sl/options.go +++ b/sl/options.go @@ -16,12 +16,23 @@ package sl -// Options contains the individual query parameters that can be applied to -// a request. +import ( + "math" +) + +// Options contains the individual query parameters that can be applied to a request. type Options struct { Id *int Mask string Filter string Limit *int Offset *int + TotalItems int +} + +// returns Math.Ciel((TotalItems - Limit) / Limit) +func (opt Options) GetRemainingAPICalls() int { + Total := float64(opt.TotalItems) + Limit := float64(*opt.Limit) + return int(math.Ceil((Total - Limit) / Limit )) }