emayecs | 5656b2b | 2021-08-04 12:44:13 -0400 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
emayecs | 5966a53 | 2021-07-29 10:07:02 -0400 | [diff] [blame] | 2 | # |
| 3 | # cace_design_upload.py |
| 4 | # |
| 5 | # The purpose of this script is to package up the user |
| 6 | # design schematic and associated files and send them to |
| 7 | # the remote marketplace server, precipitating a launch |
| 8 | # of CACE to officially characterize the design. |
| 9 | # |
| 10 | |
| 11 | import os |
| 12 | import json |
| 13 | import re |
| 14 | import sys |
| 15 | import requests |
| 16 | import subprocess |
| 17 | |
| 18 | import file_compressor |
| 19 | import file_request_hash |
| 20 | import local_uid_services |
| 21 | |
emayecs | b2487ae | 2021-08-05 10:30:13 -0400 | [diff] [blame] | 22 | import config |
emayecs | 5966a53 | 2021-07-29 10:07:02 -0400 | [diff] [blame] | 23 | |
| 24 | """ |
emayecs | 1474831 | 2021-08-05 14:21:26 -0400 | [diff] [blame] | 25 | standalone script. |
emayecs | 5966a53 | 2021-07-29 10:07:02 -0400 | [diff] [blame] | 26 | Makes rest calls to marketplace REST server to save datasheet |
| 27 | and associated file(s). Request hash is generated so the two |
| 28 | requests can be associated on the server side. This action |
| 29 | causes the marketplace to run the official characterization |
| 30 | on the CACE server. |
| 31 | """ |
| 32 | |
emayecs | b2487ae | 2021-08-05 10:30:13 -0400 | [diff] [blame] | 33 | mktp_server_url = config.mktp_server_url |
| 34 | cace_server_url = config.cace_server_url |
emayecs | 5966a53 | 2021-07-29 10:07:02 -0400 | [diff] [blame] | 35 | |
| 36 | # Make request to server sending json passed in. |
| 37 | def send_doc(doc): |
| 38 | result = requests.post(mktp_server_url + '/cace/simulate_request', json=doc) |
| 39 | print('send_doc', result.status_code) |
| 40 | |
| 41 | # Cancel simulation (sent directly to CACE) |
| 42 | def send_cancel_doc(doc): |
| 43 | result = requests.post(cace_server_url + '/cace/cancel_sims', json=doc) |
| 44 | print('send_cancel_doc', result.status_code) |
| 45 | |
| 46 | # Pure HTTP post here. Add the file to files object and the hash/filename |
| 47 | # to the data params. |
| 48 | def send_file(hash, file, file_name): |
| 49 | files = {'file': file.getvalue()} |
| 50 | data = {'request-hash': hash, 'file-name': file_name} |
| 51 | result = requests.post(mktp_server_url + '/cace/simulate_request_files', files=files, data=data) |
| 52 | print('send_file', result.status_code) |
| 53 | |
| 54 | if __name__ == '__main__': |
| 55 | |
| 56 | # Divide up command line into options and arguments |
| 57 | options = [] |
| 58 | arguments = [] |
| 59 | for item in sys.argv[1:]: |
| 60 | if item.find('-', 0) == 0: |
| 61 | options.append(item) |
| 62 | else: |
| 63 | arguments.append(item) |
| 64 | |
| 65 | # There should be two arguments passed to the script. One is |
| 66 | # the path and filename of the datasheet JSON file, and the |
| 67 | # other a path to the location of the design (netlist and/or |
| 68 | # schematic). |
| 69 | |
| 70 | datasheet_filepath = [] |
| 71 | design_filepath = [] |
| 72 | |
| 73 | for argval in arguments: |
| 74 | if os.path.isfile(argval): |
| 75 | datasheet_filepath = argval |
| 76 | elif os.path.isdir(argval): |
| 77 | design_filepath = argval |
| 78 | elif os.path.splitext(argval)[1] == '': |
| 79 | argname = argval + '.json' |
| 80 | if os.path.isfile(argval): |
| 81 | datasheet_filepath = argname |
| 82 | |
| 83 | if not datasheet_filepath: |
| 84 | # Check for JSON file 'project.json' in the current or parent directory |
| 85 | if design_filepath: |
| 86 | argtry = design_filepath + '/project.json' |
| 87 | if os.path.isfile(argtry): |
| 88 | datasheet_filepath = argtry |
| 89 | else: |
| 90 | argtry = os.path.split(design_filepath)[0] + '/project.json' |
| 91 | if os.path.isfile(argtry): |
| 92 | datasheet_filepath = argtry |
| 93 | |
| 94 | if not os.path.isfile(datasheet_filepath): |
| 95 | # Legacy behavior support: |
| 96 | # Check for JSON file with same name as netlist filepath, |
| 97 | # but with a .json extension, in the netlist filepath directory |
| 98 | # or the directory above it. |
| 99 | if design_filepath: |
| 100 | argtry = design_filepath + '/' + os.path.basename(design_filepath) + '.json' |
| 101 | if os.path.isfile(argtry): |
| 102 | datasheet_filepath = argtry |
| 103 | else: |
| 104 | argtry = os.path.split(design_filepath)[0] + '/' + os.path.basename(design_filepath) + '.json' |
| 105 | if os.path.isfile(argtry): |
| 106 | datasheet_filepath = argtry |
| 107 | |
| 108 | if not datasheet_filepath: |
| 109 | print('Error: No datasheet JSON file specified\n') |
| 110 | sys.exit(1) |
| 111 | |
| 112 | if not os.path.isfile(datasheet_filepath): |
| 113 | print('Error: No datasheet JSON file ' + datasheet_filepath + ' found\n') |
| 114 | sys.exit(1) |
| 115 | |
| 116 | # Read the datasheet now. Get the expected design name |
| 117 | |
| 118 | dsheet = {} |
| 119 | print('Reading JSON datasheet ' + datasheet_filepath) |
| 120 | with open(datasheet_filepath, 'r') as user_doc_file: |
| 121 | docinfo = json.load(user_doc_file) |
| 122 | dsheet = docinfo['data-sheet'] |
| 123 | name = dsheet['ip-name'] |
| 124 | |
| 125 | # Get JSON file of settings if it exists. It should be in the same |
emayecs | b2487ae | 2021-08-05 10:30:13 -0400 | [diff] [blame] | 126 | # location as the JSON datasheet file (generated by cace.py) |
emayecs | 5966a53 | 2021-07-29 10:07:02 -0400 | [diff] [blame] | 127 | testmode = False |
| 128 | force = False |
| 129 | settings_filepath = os.path.split(datasheet_filepath)[0] + '/settings.json' |
| 130 | if os.path.exists(settings_filepath): |
| 131 | with open(settings_filepath, 'r') as user_settings_file: |
| 132 | settings = json.load(user_settings_file) |
| 133 | docinfo['settings'] = settings |
| 134 | if 'submit-as-schematic' in settings: |
| 135 | if settings['submit-as-schematic'] == True: |
| 136 | force = True |
| 137 | if 'submit-test-mode' in settings: |
| 138 | if settings['submit-test-mode'] == True: |
| 139 | testmode = True |
| 140 | |
| 141 | # Use of "-force" in the options overrides any settings from the JSON file. |
| 142 | if '-force' in options: |
| 143 | force = True |
| 144 | |
| 145 | if '-test' in options: |
| 146 | testmode = True |
| 147 | |
| 148 | # Diagnostic |
| 149 | if 'identifiers' in docinfo: |
| 150 | print('Identifiers: ' + str(docinfo['identifiers'])) |
| 151 | |
| 152 | if not design_filepath: |
| 153 | print('Error: No schematic or netlist directory given\n') |
| 154 | sys.exit(1) |
| 155 | else: |
| 156 | # If design_filepath has a subdirectory "design", add that to |
| 157 | # the path name. |
| 158 | if os.path.isdir(design_filepath + '/spi'): |
| 159 | spice_filepath = design_filepath + '/spi' |
| 160 | filelist = os.listdir(spice_filepath) |
| 161 | if os.path.isdir(design_filepath + '/elec'): |
| 162 | if os.path.isdir(design_filepath + '/elec/' + name + '.delib'): |
| 163 | schem_filepath = design_filepath + '/elec/' + name + '.delib' |
| 164 | filelist.extend(os.listdir(schem_filepath)) |
| 165 | |
| 166 | # To be valid, the filepath must contain either a .spi file with |
| 167 | # the name of ip-name, or a .sch file with the name of ip-name. |
| 168 | netlistname = name + '.spi' |
| 169 | schemname = name + '.sch' |
| 170 | if netlistname not in filelist and schemname not in filelist: |
| 171 | print('Error: Path ' + design_filepath + ' has no schematic ' |
| 172 | + 'or netlist for design ' + name + '\n') |
| 173 | sys.exit(1) |
| 174 | |
| 175 | # Add key 'project-folder' to the document, containing the path to the |
| 176 | # JSON file. This is used by the CACE to ensure that progress information |
| 177 | # is passed to the correct folder, not relying on hard-coded home paths, |
| 178 | # and allowing for copies of projects in different paths. |
| 179 | |
| 180 | foldername = os.path.split(datasheet_filepath)[0] |
| 181 | docinfo['project-folder'] = foldername |
| 182 | |
| 183 | # Current expectation is to use UID (username). If it is not in the |
| 184 | # document, then add it here. |
| 185 | |
| 186 | if testmode: |
| 187 | uid = {} |
| 188 | else: |
| 189 | if 'UID' not in docinfo: |
| 190 | uid = local_uid_services.get_uid(os.environ['USER']) |
| 191 | else: |
| 192 | uid = docinfo['UID'] |
| 193 | |
| 194 | if not uid or uid == 'null': |
| 195 | uid = os.environ['USER'] |
| 196 | docinfo['UID'] = uid |
| 197 | |
| 198 | # Handle cancel requests |
| 199 | if '-cancel' in options: |
| 200 | # Read last message . . . |
| 201 | if os.path.exists(design_filepath + '/ngspice/char/remote_status.json'): |
| 202 | with open(design_filepath + '/ngspice/char/remote_status.json', 'r') as f: |
| 203 | status = json.load(f) |
| 204 | if 'hash' in status: |
| 205 | docinfo['request-hash'] = status['hash'] |
| 206 | send_cancel_doc(docinfo) |
| 207 | else: |
| 208 | print('No hash value in status file, cannot cancel.') |
| 209 | else: |
| 210 | print('No status file found, cannot cancel.') |
| 211 | sys.exit(0) |
| 212 | |
| 213 | # If settings specify that the submission should be forced to be schematic-only, |
| 214 | # pass the setting to CACE as 'netlist-source' in the data-sheet record. |
| 215 | if force: |
| 216 | dsheet['netlist-source'] = 'schematic' |
| 217 | |
| 218 | # Put the current git system state into the target directory |
| 219 | # prior to tarballing |
| 220 | if os.path.isfile('/ef/.ef-version'): |
| 221 | with open('/ef/.ef-version', 'r') as f: |
| 222 | ef_version = f.read().rstrip() |
| 223 | docinfo['ef-version'] = ef_version |
| 224 | |
| 225 | rhash, timestamp = file_request_hash.get_hash(name) |
| 226 | docinfo['request-hash'] = rhash |
| 227 | print('request hash = ' + rhash + '\n') |
| 228 | |
| 229 | # Now send the document |
| 230 | if testmode: |
| 231 | print('Test: running send_doc(docinfo)\n') |
| 232 | else: |
| 233 | send_doc(docinfo) |
| 234 | |
| 235 | # Send the tarballed design file directory to the marketplace server for storage. |
| 236 | # Ignore the log file, which is meant for in-system diagnostics, not for storage. |
| 237 | exclusions = [name + '\.log', '.*\.raw', |
| 238 | 'elec/\.java', |
| 239 | 'ngspice/run/\.allwaves'] |
| 240 | |
| 241 | # If settings specify that the submission should be forced to be schematic-only, |
| 242 | # then don't tarball the layout database files. |
| 243 | if force: |
| 244 | exclusions.append('mag/.*\.mag') |
| 245 | exclusions.append('mag/.*\.ext') |
| 246 | |
| 247 | # Now send the netlist file tarball |
| 248 | tarballname = name + '.tar.gz' |
| 249 | if testmode: |
| 250 | file_compressor.tar_directory_contents_to_file(design_filepath, |
| 251 | tarballname, exclude=exclusions) |
| 252 | os.rename(design_filepath + '/' + tarballname, tarballname) |
| 253 | print('Test: running send_file(' + rhash + ', <tarball>, ' + tarballname + ')\n') |
| 254 | else: |
| 255 | tar = file_compressor.tar_directory_contents(design_filepath, |
| 256 | exclude=exclusions) |
| 257 | send_file(rhash, tar, tarballname) |
| 258 | |