Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# (c) Stefan Countryman, 2019 

2 

3""" 

4``FileHandler`` classes that log file uploads to Slack. 

5 

6Slack API documentation: 

7https://api.slack.com/methods/chat.postMessage 

8https://api.slack.com/messaging/composing/formatting# 

9 

10(Link to permissions in top right of the above pages.) 

11""" 

12 

13import json 

14import socket 

15import logging 

16from llama.flags import FlagDict 

17from llama.filehandler import ( 

18 GenerationError, 

19 JSONFile, 

20) 

21from llama.com.utils import UploadReceipt 

22from llama import detectors 

23from llama.files.skymap_info import ( 

24 SkymapInfo, 

25 gracedb_url, 

26) 

27from llama.serve.gui import gui_url 

28# have to import some things that SkymapInfo depends on so that we can make 

29# uploads for them here. 

30from llama.files.lvc_gcn_xml import LvcGcnXml, LvcRetractionXml 

31from llama.files.lvalert_json import LVAlertJSON 

32from llama.com.slack import ( 

33 SLACK_CHANNELS, 

34 SLACK_TOKENS, 

35 client, 

36) 

37 

38LOGGER = logging.getLogger(__name__) 

39# initialize an empty list of org-specific vetoes and populate it below 

40VETOES = {obs: list() for obs in SLACK_CHANNELS} 

41FMT = """VERSIONED_FILENAME: *{filename_for_download}* 

42FILENAME: {filename} 

43GRACEID: `{graceid}` 

44LLAMAID: `{eventid}` 

45GRACEDB_LINK: {link} 

46LLAMA_LINK: {llama_url} 

47ANALYSIS_SERVER: {host} 

48EVENT_DIRECTORY: {eventdir} 

49UPLOAD_FILEHANDLER: `{uploadhandler}` 

50UPLOADER: `{selftype}` 

51WALL_TIME: {walltime} sec 

52EVENT_SNAPSHOT: {githash} 

53TAGS: #{graceid} #{eventid} #{filename}""" 

54 

55 

56def icecube_upload_flag_false(eventdir): 

57 """Return whether a trigger directory has its "ICECUBE_UPLOAD" flag set to 

58 "false".""" 

59 return FlagDict(eventdir)['ICECUBE_UPLOAD'] == 'false' 

60 

61 

62VETOES["IceCube"].append(icecube_upload_flag_false) 

63 

64 

65# pylint: disable=too-many-ancestors,duplicate-bases 

66class SlackReceipt(UploadReceipt, JSONFile): 

67 """ 

68 A log file created after an upload attempt to Slack. Use these 

69 filehandlers to upload files to slack. Additional vetoes that turn a 

70 specific upload receipt off are specified in ``VETOES``. 

71 """ 

72 

73 ORGANIZATION = None 

74 

75 _REQUIRED = ('ORGANIZATION',) 

76 

77 @staticmethod 

78 def _classname_format(org): 

79 """Get the ``CLASSNAME_FMT`` attribute for organization ``org``.""" 

80 assert org in SLACK_CHANNELS 

81 return f"RctSlk{getattr(detectors, org).abbrev.capitalize()}{{}}" 

82 

83 @classmethod 

84 def set_class_attributes(cls, subclass): 

85 """See ``UploadReceipt.set_class_attributes``; this method first sets 

86 the ``FILENAME_FMT`` and ``CLASSNAME_FMT`` attributes based on 

87 ``subclass.ORGANIZATION``. Also adds ``SkymapInfo`` to 

88 ``subclass.DEPENDENCIES`` if it is not already a member and sets 

89 ``class_vetoes`` to the ones defined for this organization. 

90 """ 

91 abbrev = getattr(detectors, subclass.ORGANIZATION).abbrev.lower() 

92 fmt = f"rct_slk_{abbrev}_{{}}.json" 

93 subclass.FILENAME_FMT = fmt 

94 setattr(subclass, 'class_vetoes', 

95 tuple((v, None) for v in VETOES[subclass.ORGANIZATION])) 

96 super().set_class_attributes(subclass) 

97 for attr in ['DEPENDENCIES', 'UR_DEPENDENCIES']: 

98 if SkymapInfo not in getattr(subclass, attr): 

99 setattr(subclass, attr, tuple(list(getattr(subclass, attr)) + 

100 [SkymapInfo])) 

101 return subclass 

102 

103 @property 

104 def upload_title(self): 

105 """Print a title for this upload.""" 

106 return "{} from {} (LLAMA internal ID: {})".format( 

107 self.UPLOAD.FILENAME, 

108 SkymapInfo(self).graceid, 

109 self.eventid, 

110 ) 

111 

112 @property 

113 def comment(self): 

114 """An explanation of this upload ``FileHandler`` to be printed to team 

115 slack.""" 

116 skyinfo = SkymapInfo(self) 

117 link = gracedb_url(skyinfo.graceid) 

118 calling_fh = self if (self.parent is None) else self.parent 

119 upload = self.UPLOAD(calling_fh) # pylint: disable=not-callable 

120 return FMT.format( 

121 filename_for_download=upload.filename_for_download, 

122 filename=upload.FILENAME, 

123 llama_url=gui_url(upload), 

124 uploadhandler=type(upload).__name__, 

125 selftype=type(self).__name__, 

126 graceid=skyinfo.graceid, 

127 link=link, 

128 eventid=self.eventid, 

129 eventdir=calling_fh.eventdir, 

130 walltime=upload.meta.wall_time, 

131 githash=calling_fh.git.current_hash, 

132 host=socket.gethostname(), 

133 ) 

134 

135 def _generate(self): 

136 from slack.errors import SlackApiError 

137 calling_fh = self if (self.parent is None) else self.parent 

138 upload = self.UPLOAD(calling_fh) # pylint: disable=not-callable 

139 res = dict() 

140 for chan in SLACK_CHANNELS[self.ORGANIZATION]: 

141 with upload.open('rb') as upfile: 

142 try: 

143 clnt = client(SLACK_TOKENS[self.ORGANIZATION]) 

144 res[chan] = clnt.files_upload( 

145 channels=chan, # only upload to one channel at a time 

146 filename=upload.filename_for_download, 

147 file=upfile, 

148 title=self.upload_title, 

149 initial_comment=self.comment, 

150 ) 

151 if not (res[chan].get('ok') and 

152 res[chan].status_code == 200): 

153 msg = (f"Error while uploading {upload}. status: " 

154 f"{res[chan].status_code}, response: " 

155 f"{res[chan].data}") 

156 LOGGER.error(msg) 

157 raise SlackApiError(msg, res[chan]) 

158 except SlackApiError as err: 

159 LOGGER.error("Error while uploading: %s", err) 

160 raise GenerationError(str(err)) 

161 self._write_json({k: v.data for k, v in res.items()}) 

162 

163 

164class SlackReceiptLlama(SlackReceipt): 

165 """A ``SlackReceipt`` for organization ``LLAMA``.""" 

166 

167 ORGANIZATION = "LLAMA" 

168 CLASSNAME_FMT = SlackReceipt._classname_format(ORGANIZATION) 

169 

170 

171class SlackReceiptIcecube(SlackReceipt): 

172 """A ``SlackReceipt`` for organization ``IceCube``.""" 

173 

174 ORGANIZATION = "IceCube" 

175 CLASSNAME_FMT = SlackReceipt._classname_format(ORGANIZATION) 

176 

177 

178# register SkymapInfo as a slack upload 

179SlackReceiptLlama.upload_this()(SkymapInfo) 

180SlackReceiptLlama.upload_this()(LvcGcnXml) 

181SlackReceiptLlama.upload_this()(LvcRetractionXml) 

182SlackReceiptLlama.upload_this()(LVAlertJSON)