Last active
August 29, 2015 14:03
-
-
Save pearj/6006820a22e3ecad6df8 to your computer and use it in GitHub Desktop.
Rackspace Alarm Suppression via script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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