// file: check_usage_internode.go // Version 0.3 (05/08/2016) // // check_usage_internode is a Nagios plugin made by Edwin van Ree (support at vanree.com) // to monitor Internode Internet download usage. // // I have used the Google Go programming language because of no need to install any libraries. // This script needs to be compiled with go build check_uage_adam.go on your target platform (e.g. Ubuntu). // // The plug-in uses the Internode Internet XML API via HTTPS to check the amount of download limit left. // Note: If you wish to create your own usage meter interface, do not copy the // interface from this program, please contact Internode via // http://www.internode.on.net/contact/support/ for the API document. // // This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY. // It may be used, redistributed and/or modified under the terms of the GNU // General Public Licence (see http://www.fsf.org/licensing/licenses/gpl.txt). // // tested with: // 1. Internode Internet API version 1.5 // // changelog: // Version 0.3 (05/08/2016) allow quota of 0 to be unlimited // Version 0.2 (30/08/2013) added days until rollover in usage_percent and compile notice // Version 0.1 (21/08/2013) initial release (kindly started from check_usage_adam.go) // // todo: // add history bit // // flags: // -u=<username> Internode Internet user name // -p=<account name> Internode Internet password // -c=<critical level> Critical level value, default = 90 // -w=<warning level> Warning level value, default = 70 // -i=<info> Info to retrieve, values of <info> are: // services List of services, use this command first to determine your SERVICE_ID (SID) for info below // resources List of resources for given SID // usage_percent Calculate the used percentage of the downloaded traffic, return value set according the given critical and warning levels // usage Return the quota and used amount in MB + percentage gone + rollover date and plan interval, return value always OK // service_plan Return the service plan info (plan, carrier, speed, rating & cost), return value always OK // service_excess Return the service excess info (cost, charged, shaped, restrict), return value always OK // -SID=<sid> Service ID number for detailed info requests // -d=<level> print debug, level: 1 errors only, 2 warnings and 3 informational messages // -E print environment variables for debug purpose // -V print plugin version // // usage examples: // // Get list of services on the given Internode account: // $ go run check_usage_internode -u=username -p=password // Internode - OK - U=username,Services=1:(1234567) // // Get usage info for the given Internode account and service: // $ go run check_usage_internode -u=username -p=password -SID=1234567 -i=usage // Internode - OK - U=username,SID=1234567,Usage:(quota=300000MB, used=150000MB, gone=50%, rollover=2013-10-01, interval=Monthly) // package main import ( "crypto/tls" "encoding/xml" "flag" "fmt" "io/ioutil" "log" "net/http" "os" "path" "strconv" "time" ) const ( version = "0.1" debug_Error = 1 debug_Warning = 2 debug_Info = 3 ) type ( Services struct { XMLName xml.Name `xml:"services"` Count string `xml:"count,attr"` Service []string `xml:"service"` } API1 struct { XMLName xml.Name `xml:"api"` Services Services } Internode1 struct { XMLName xml.Name `xml:"internode"` API API1 } Resources struct { XMLName xml.Name `xml:"resources"` Count string `xml:"count,attr"` Resource []string `xml:"resource"` } API2 struct { XMLName xml.Name `xml:"api"` Service string `xml:"service"` Resources Resources } Internode2 struct { XMLName xml.Name `xml:"internode"` API API2 } Traffic struct { XMLName xml.Name `xml:"traffic"` Name string `xml:"name,attr"` Unit string `xml:"unit,attr"` Rollover string `xml:"rollover,attr"` PlanInterval string `xml:"plan-interval,attr"` Quota string `xml:"quota,attr"` Value string `xml:",innerxml"` } API3 struct { XMLName xml.Name `xml:"api"` Service string `xml:"service"` Traffic Traffic } Internode3 struct { XMLName xml.Name `xml:"internode"` API API3 } Usage struct { XMLName xml.Name `xml:"usage"` Day string `xml:"day,attr"` Traffic Traffic } UsageList struct { XMLName xml.Name `xml:"usagelist"` Usage []Usage } API4 struct { XMLName xml.Name `xml:"api"` Service string `xml:"service"` UsageList UsageList } Internode4 struct { XMLName xml.Name `xml:"internode"` API API4 } Service struct { XMLName xml.Name `xml:"service"` Type string `xml:"type,attr"` Request string `xml:"request,attr"` ID string `xml:"id"` Username string `xml:"username"` Quota string `xml:"quota"` Plan string `xml:"plan"` Carrier string `xml:"carrier"` Speed string `xml:"speed"` UsageRating string `xml:"usage-rating"` Rollover string `xml:"rollover"` ExcessCost string `xml:"excess-cost"` ExcessCharged string `xml:"excess-charged"` ExcessShaped string `xml:"excess-shaped"` ExcessRestrictAccess string `xml:"excess-restrict-access"` PlanInterval string `xml:"plan-interval"` PlanCost string `xml:"plan-cost"` } API5 struct { XMLName xml.Name `xml:"api"` Service Service } Internode5 struct { XMLName xml.Name `xml:"internode"` API API5 } ) var ( user string password string debug int showEnv bool showVersion bool proxyString string critical int warning int info string percentF float32 IPaddress string SID string count int64 i int64 downloadUsage int64 downloadQuota int64 err error ) func debugPrintf(level int, format string, a ...interface{}) { if level <= debug { log.Printf(format, a...) } } func init() { flag.StringVar(&user, "u", "<must be specified>", "Internode Internet username") flag.StringVar(&password, "p", "<must be specified>", "Internode Internet password") flag.IntVar(&debug, "d", 0, "print debug, level: 1 errors only, 2 warnings and 3 informational messages") flag.BoolVar(&showEnv, "E", false, "print environment variables for debug purpose") flag.BoolVar(&showVersion, "V", false, "print plugin version") flag.StringVar(&proxyString, "P", "", "proxy URL") flag.StringVar(&info, "i", "services", "Info requested") flag.StringVar(&IPaddress, "IP", "<must be specified>", "IP address to check") flag.IntVar(&critical, "c", 90, "critical level") flag.IntVar(&warning, "w", 70, "warning level") flag.StringVar(&SID, "SID", "<must be specified>", "Service ID to check") } func SendRequest(reqType int) (body []byte) { // build the URL for the request type url := "https://customer-webtools-api.internode.on.net/api/v1.5/" switch reqType { case 1: // Services case 2: // Resources url += fmt.Sprintf("%s", SID) case 3: // Usage url += fmt.Sprintf("%s", SID) url += "/usage" case 4: // History url += fmt.Sprintf("%s", SID) url += "/history" case 5: // Service url += fmt.Sprintf("%s", SID) url += "/service" } debugPrintf(debug_Info, "url: %s\n", url) client := &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, } req, err := http.NewRequest("GET", url, nil) req.SetBasicAuth(user, password) req.Header.Set("User-Agent", "NagiosCheckUsage/0.01") resp, err := client.Do(req) if err != nil { log.Fatal(err) } body, err = ioutil.ReadAll(resp.Body) if err != nil { log.Fatal(err) } resp.Body.Close() debugPrintf(debug_Info, "http status code: %s\n", resp.Status) debugPrintf(debug_Info, "http body: \n%s\n", body) return body } func main() { flag.Parse() // send errors to Stdout instead to Stderr // http://nagiosplug.sourceforge.net/developer-guidelines.html#PLUGOUTPUT log.SetOutput(os.Stdout) if showEnv { log.Printf("** environment variables start **\n") for _, v := range os.Environ() { log.Printf("%s\n", v) } log.Printf("** environment variables end **\n") } if showVersion { fmt.Printf("%s version: %s\n", path.Base(os.Args[0]), version) os.Exit(0) } output := "Internode - " ret_val := 0 switch info { case "services": body := SendRequest(1) xmlResponse := &Internode1{} err := xml.Unmarshal(body, &xmlResponse) if err != nil { fmt.Printf("Decoder error: %v\n", err) os.Exit(3) } debugPrintf(debug_Info, "Response: \n%#v\n", xmlResponse) debugPrintf(debug_Info, "# Services: %d\n", xmlResponse.API.Services.Count) output += "OK - U=" output += fmt.Sprintf("%s", user) output += ",Services=" count, err = strconv.ParseInt(xmlResponse.API.Services.Count, 10, 32) if err != nil { fmt.Printf("Count conversion error: %v\n", err) os.Exit(3) } output += fmt.Sprintf("%d", count) output += ":(" for i = 0; i < count; i++ { if i > 0 {output += ","} output += fmt.Sprintf("%s", xmlResponse.API.Services.Service[i]) } output += ")" case "resources": body := SendRequest(2) xmlResponse := &Internode2{} err := xml.Unmarshal([]byte(body), &xmlResponse) if err != nil { fmt.Printf("Decoder error: %v\n", err) os.Exit(3) } debugPrintf(debug_Info, "Response: \n%#v\n", xmlResponse) debugPrintf(debug_Info, "# Services: %d\n", xmlResponse.API.Resources.Count) output += "OK - U=" output += fmt.Sprintf("%s", user) output += ",SID=" output += fmt.Sprintf("%s", SID) output += ",Resources=" count, err = strconv.ParseInt(xmlResponse.API.Resources.Count, 10, 32) if err != nil { fmt.Printf("Count conversion error: %v\n", err) os.Exit(3) } output += fmt.Sprintf("%d", count) output += ":(" for i = 0; i < count; i++ { if i > 0 {output += ","} output += fmt.Sprintf("%s", xmlResponse.API.Resources.Resource[i]) } output += ")" case "usage": body :=SendRequest(3) xmlResponse := &Internode3{} err := xml.Unmarshal([]byte(body), &xmlResponse) if err != nil { fmt.Printf("Decoder error: %v\n", err) os.Exit(3) } debugPrintf(debug_Info, "Response: \n%#v\n", xmlResponse) output += "OK - U=" output += fmt.Sprintf("%s", user) output += ",SID=" output += fmt.Sprintf("%s", SID) downloadUsage, err = strconv.ParseInt(xmlResponse.API.Traffic.Value, 10, 64) if err != nil { fmt.Printf("Usage conversion error: %v\n", err) os.Exit(3) } downloadQuota, err = strconv.ParseInt(xmlResponse.API.Traffic.Quota, 10, 64) if err != nil { fmt.Printf("Quota conversion error: %v\n", err) os.Exit(3) } output += ",Usage:(quota=" if downloadQuota == 0 { output += "unlimited " } else { output += fmt.Sprintf("%d", downloadQuota / 1000000) } output += "MB, used=" output += fmt.Sprintf("%d", downloadUsage / 1000000) output += "MB, gone=" if downloadQuota == 0 { output += "N/A" } else { percentF = float32(downloadUsage) / float32(downloadQuota) * 100.0 percent := int(percentF) output += fmt.Sprintf("%d", percent ) } output += "%, rollover=" output += fmt.Sprintf("%s", xmlResponse.API.Traffic.Rollover) output += ", interval=" output += fmt.Sprintf("%s", xmlResponse.API.Traffic.PlanInterval) output += ")" case "usage_percent": body := SendRequest(3) xmlResponse := &Internode3{} err = xml.Unmarshal([]byte(body), &xmlResponse) if err != nil { fmt.Printf("Decoder error: %v\n", err) os.Exit(3) } debugPrintf(debug_Info, "Response: \n%#v\n", xmlResponse) downloadUsage, err = strconv.ParseInt(xmlResponse.API.Traffic.Value, 10, 64) if err != nil { fmt.Printf("Usage conversion error: %v\n", err) os.Exit(3) } rolloverString := xmlResponse.API.Traffic.Rollover debugPrintf(debug_Info, "Rollover string: %s\n", rolloverString) const longForm = "2006-01-02" rolloverDate, err := time.Parse(longForm, rolloverString) if err != nil { fmt.Printf("Rollover Date conversion error: %v\n", err) os.Exit(3) } debugPrintf(debug_Info, "Rollover Date: %v\n", rolloverDate) timeToGo := rolloverDate.Sub(time.Now()) daysToGo := timeToGo.Hours() / 24 debugPrintf(debug_Info, "Time to go: %v\n", daysToGo) downloadQuota, err = strconv.ParseInt(xmlResponse.API.Traffic.Quota, 10, 64) if err != nil { fmt.Printf("Quota conversion error: %v\n", err) os.Exit(3) } if downloadQuota == 0 { output += "OK - Quota was 0 (UNLIMITED)" ret_val = 0 } else { percentF = float32(downloadUsage) / float32(downloadQuota) * 100.0 percent := int(percentF) debugPrintf(debug_Info, "Download Percent: %d\n", percent) switch { case percent >= critical: output += "CRITICAL" ret_val = 2 case percent >= warning: output += "WARNING" ret_val = 1 default: output += "OK" ret_val = 0 } output += " - Used: " output += fmt.Sprintf("%d", percent) output += "% (" output += fmt.Sprintf("%3.1f", daysToGo) output += " days to go)" } case "service_plan": body :=SendRequest(5) xmlResponse := &Internode5{} err := xml.Unmarshal([]byte(body), &xmlResponse) if err != nil { fmt.Printf("Decoder error: %v\n", err) os.Exit(3) } debugPrintf(debug_Info, "Response: \n%#v\n", xmlResponse) output += "OK - U=" output += fmt.Sprintf("%s", user) output += ",SID=" output += fmt.Sprintf("%s", SID) output += ",Service:(plan=" output += fmt.Sprintf("%s", xmlResponse.API.Service.Plan) output += ", carrier=" output += fmt.Sprintf("%s", xmlResponse.API.Service.Carrier) output += ", speed=" output += fmt.Sprintf("%s", xmlResponse.API.Service.Speed) output += ", rating=" output += fmt.Sprintf("%s", xmlResponse.API.Service.UsageRating) output += ", cost=" output += fmt.Sprintf("%s", xmlResponse.API.Service.PlanCost) output += ")" case "service_excess": body :=SendRequest(5) xmlResponse := &Internode5{} err := xml.Unmarshal([]byte(body), &xmlResponse) if err != nil { fmt.Printf("Decoder error: %v\n", err) os.Exit(3) } debugPrintf(debug_Info, "Response: \n%#v\n", xmlResponse) output += "OK - U=" output += fmt.Sprintf("%s", user) output += ",SID=" output += fmt.Sprintf("%s", SID) output += ",Service-excess:(cost=" output += fmt.Sprintf("%s", xmlResponse.API.Service.ExcessCost) output += ", charged=" output += fmt.Sprintf("%s", xmlResponse.API.Service.ExcessCharged) output += ", shaped=" output += fmt.Sprintf("%s", xmlResponse.API.Service.ExcessShaped) output += ", restrict=" output += fmt.Sprintf("%s", xmlResponse.API.Service.ExcessRestrictAccess) output += ")" default: fmt.Printf("Unknown Info request name: %s\n", info) os.Exit(3) } fmt.Printf("%s\n", output) os.Exit(ret_val) }