Skip to content

Instantly share code, notes, and snippets.

@pearj
Last active August 29, 2015 14:03
Show Gist options
  • Save pearj/6006820a22e3ecad6df8 to your computer and use it in GitHub Desktop.
Save pearj/6006820a22e3ecad6df8 to your computer and use it in GitHub Desktop.
Rackspace Alarm Suppression via script
"""
Script to create/delete/list Alarm Suppression for Rackspace
Since rackspace don't appear to have an API to manage Alarm Suppressions this script uses the regular web interface.
If rackspace change their web interface this script will stop working.
Web page parsing is done using http://www.crummy.com/software/BeautifulSoup/
Beautiful Soup can be installed using: sudo pip install beautifulsoup4
Http communication is done using Requests: http://docs.python-requests.org/en/latest/
It can be upgraded to v2.3.0 using: sudo pip install --upgrade requests
Operation examples:
Create:
python rackspace-suppression.py --account='1234567' --username='username' --password='password' --create "55123,55456,55789"
>>>123456789 <- This is the suppression id
List:
python rackspace-suppression.py --account='1234567' --username='username' --password='password' --list
>>>QA deploy 1404963991000|55123-server1,55456-server2,55789-server3|2014-07-11 00:00:00|2014-07-11 00:10:00|123456789
Delete:
python rackspace-suppression.py --account='1234567' --username='username' --password='password' --delete 123456789 <- the suppression id
"""
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.poolmanager import PoolManager
from bs4 import BeautifulSoup
import ssl
import requests
import datetime
import calendar
import re
import sys
import getopt
import time
# Fix for forcing TLSv1 from https://lukasa.co.uk/2013/01/Choosing_SSL_Version_In_Requests/
class MyAdapter(HTTPAdapter):
def init_poolmanager(self, connections, maxsize, block=False):
self.poolmanager = PoolManager(num_pools=connections,
maxsize=maxsize,
block=block,
ssl_version=ssl.PROTOCOL_TLSv1)
class RackspaceSoup:
@staticmethod
def parseUnixTimeToString(strtime):
unixTime = int(strtime) / 1000
return datetime.datetime.fromtimestamp(unixTime).strftime('%Y-%m-%d %H:%M:%S')
@staticmethod
def parseSuppressionList(content):
soup = BeautifulSoup(content)
suppressions = soup.select("li.suppression")
formattedSuppressions = []
for suppression in suppressions:
reason = suppression.select("span.suppression-reason")[0].get_text()
cancel = suppression.select("a.myrs-confirm")[0]['href']
matches = re.findall(r"/([0-9]+)$",cancel)
suppressionId = matches[0]
devices = suppression.select("ul.suppression-devices li")
devicesText = [d.get_text() for d in devices]
deviceString = ",".join(devicesText)
window = suppression.select("span.suppression-window span.myrs-datetime-local")
windows = [RackspaceSoup.parseUnixTimeToString(w['title']) for w in window]
supp = {"reason": reason, "devices": devicesText, "startTime": windows[0], "endTime": windows[1], "suppressionId": suppressionId}
formattedSuppressions.append(supp)
return formattedSuppressions
@staticmethod
def findSuppressionReasonById(listContent, suppressionId):
soup = BeautifulSoup(listContent)
suppressions = soup.select("li.suppression")
for suppression in suppressions:
cancel = suppression.select("a.myrs-confirm")[0]['href']
if cancel.endswith(suppressionId):
reason = suppression.select("span.suppression-reason")[0].get_text()
return reason
@staticmethod
def parseTicketList(ticketResponse):
soup = BeautifulSoup(ticketResponse)
tickets = soup.select("#tickets-open td.col-subject a")
ticketInfo = []
for ticket in tickets:
subject = ticket.get_text().strip()
link = "https://my.rackspace.com" + ticket['href']
ticketInfo.append({"subject": subject, "link": link})
return ticketInfo
@staticmethod
def findTicketClosureDetails(ticketContent):
soup = BeautifulSoup(ticketContent)
hiddenElements = soup.select('form[action="/portal/ticket/update"] input[type="hidden"]')
payload = {"close":"Close Ticket"}
for hidden in hiddenElements:
payload[hidden['name']] = hidden['value']
return payload
@staticmethod
def findSuppressionIdByReason(listContent, suppressionReason):
soup = BeautifulSoup(listContent)
suppressions = soup.select("li.suppression")
for suppression in suppressions:
reason = suppression.select("span.suppression-reason")[0].get_text()
if suppressionReason == reason:
cancel = suppression.select("a.myrs-confirm")[0]['href']
matches = re.findall(r"/([0-9]+)$",cancel)
suppressionId = matches[0]
return suppressionId
class Rackspace:
def login(self, account, username, password):
self.s = requests.Session()
self.s.mount('https://', MyAdapter())
payload = {'account':account, 'username':username, 'password':password}
loginResponse = self.s.post("https://my.rackspace.com/portal/auth/signIn", data=payload)
# When Login fails it redirects you back to the login page
if loginResponse.url.startswith("https://my.rackspace.com/portal/auth/login"):
raise Exception("Incorrect account details, please check your details")
def getSuppressionList(self):
suppressionResponse = self.s.get("https://my.rackspace.com/portal/suppression/list")
suppressions = RackspaceSoup.parseSuppressionList(suppressionResponse.content)
if len(suppressions) > 0:
print "|".join(suppressions[0].keys())
for supp in suppressions:
devices = ",".join(supp['devices'])
print supp['reason'] + "|" + devices + "|" + supp['startTime'] + "|" + supp['endTime'] + "|" + supp['suppressionId']
def logout(self):
self.s.get("https://my.rackspace.com/portal/auth/signOut")
def cancelSuppression(self, suppressionId):
print "Cancelling suppression id: " + suppressionId
listResponse = self.s.get("https://my.rackspace.com/portal/suppression/list")
reason = RackspaceSoup.findSuppressionReasonById(listResponse.content, suppressionId)
self.s.get("https://my.rackspace.com/portal/suppression/cancel/" + suppressionId)
if reason != None:
self.closeTicketByReason(reason)
else:
print "Can't close related ticket because the suppression reason wasn't found"
def closeTicketByReason(self,reason):
urlparams = {"status":"open", "filter":'"' + reason + '"'}
ticketResponse = self.s.get("https://my.rackspace.com/portal/ticket/list", params=urlparams)
tickets = RackspaceSoup.parseTicketList(ticketResponse.content)
if len(tickets) == 1:
ticket = tickets[0]
if ticket['subject'] == "Suppression Request":
print "Attempting to close related ticket with reason: [" + reason + "] url: [" + ticket['link'] + "]"
if self.waitForTicketCancellation(ticket['link']):
ticketResponse = self.s.get(ticket['link'])
payload = RackspaceSoup.findTicketClosureDetails(ticketResponse.content)
self.s.post("https://my.rackspace.com/portal/ticket/update", data=payload)
else:
print "Not closing related ticket because it had an unexpected subject [" + ticket['subject'] + "]"
else:
print "Not closing related ticket because there were [" + len(tickets) + "] tickets instead of 1"
def waitForTicketCancellation(self,ticketUrl):
maxWaitTime=300
currentWaitTime=0
while currentWaitTime < maxWaitTime:
response = self.s.get(ticketUrl)
if "has been Cancelled" in response.text:
print "Cancellation message found, waiting another 30 seconds to be safe"
time.sleep(30)
return True
print "Waiting 30 seconds for ticket closure"
time.sleep(30)
currentWaitTime += 30
print "Ticket failed to go into Cancelled state"
return False
def createSuppression(self, servers, mins, reason):
start = datetime.datetime.utcnow()
end = start + datetime.timedelta(minutes=mins)
startUnixMillis = calendar.timegm(start.timetuple()) * 1000
endUnixMillis = calendar.timegm(end.timetuple()) * 1000
reason += ' ' + str(startUnixMillis)
serversSplit = servers.split(',')
payload = {'reason':reason, 'startTimestamp': startUnixMillis, 'endTimestamp':endUnixMillis, 'devices[]': serversSplit}
suppressionResponse = self.s.post("https://my.rackspace.com/portal/suppression/save", data=payload)
suppressionId = RackspaceSoup.findSuppressionIdByReason(suppressionResponse.content, reason)
print suppressionId
def usage():
print 'rackspace-suppression.py [--create <comma seperated servers identifiers no spaces>] [--delete <suppressionId>] --username <username> --account <account> --password <password> [--title <defaults to QA deploy>] [--minutes <length of suppression defaults to 30>]'
sys.exit(2)
def main(argv):
minutes = 30
account = None
username = None
password = None
servers = None
suppressionId = None
operation = None
reason = "QA deploy"
try:
opts, args = getopt.getopt(argv,"hcd:m:a:u:p:i:lr:",["minutes=","account=","username=","password=","suppressionId=","create=","delete=","list","reason="])
except getopt.GetoptError:
usage()
for opt, arg in opts:
if opt == '-h':
usage()
elif opt in ("-m", "--minutes"):
minutes = int(arg)
elif opt in ("-a", "--account"):
account = arg
elif opt in ("-u", "--username"):
username = arg
elif opt in ("-p", "--password"):
password = arg
elif opt in ("-r", "--reason"):
reason = arg
elif opt in ("-c", "--create"):
operation = "create"
servers = arg
elif opt in ("-d", "--delete"):
operation = "delete"
suppressionId = arg
elif opt in ("-l", "--list"):
operation = "list"
if None in (account, username, password):
print "You must always specificy the account, username and password"
usage()
if operation == None:
print "No operation specified"
usage()
rack = Rackspace()
rack.login(account, username, password)
if operation == "create":
rack.createSuppression(servers, minutes, reason)
elif operation == "delete":
rack.cancelSuppression(suppressionId)
elif operation == "list":
rack.getSuppressionList()
rack.logout()
if __name__ == "__main__":
main(sys.argv[1:])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment