#Licensed to the Apache Software Foundation (ASF) under one #or more contributor license agreements. See the NOTICE file #distributed with this work for additional information #regarding copyright ownership. The ASF licenses this file #to you under the Apache License, Version 2.0 (the #"License"); you may not use this file except in compliance #with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # #Unless required by applicable law or agreed to in writing, #software distributed under the License is distributed on an #"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY #KIND, either express or implied. See the License for the #specific language governing permissions and limitations #under the License. import subprocess import urllib import hmac import hashlib import base64 import traceback import logging import re from flask import Flask app = Flask(__name__) logger = logging.getLogger('baremetal-vr') hdlr = logging.FileHandler('/var/log/baremetal-vr.log') formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') hdlr.setFormatter(formatter) logger.addHandler(hdlr) logger.setLevel(logging.WARNING) class ShellCmd(object): ''' classdocs ''' def __init__(self, cmd, workdir=None, pipe=True): ''' Constructor ''' self.cmd = cmd if pipe: self.process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE, executable='/bin/sh', cwd=workdir) else: self.process = subprocess.Popen(cmd, shell=True, executable='/bin/sh', cwd=workdir) self.stdout = None self.stderr = None self.return_code = None def __call__(self, is_exception=True): (self.stdout, self.stderr) = self.process.communicate() if is_exception and self.process.returncode != 0: err = [] err.append('failed to execute shell command: %s' % self.cmd) err.append('return code: %s' % self.process.returncode) err.append('stdout: %s' % self.stdout) err.append('stderr: %s' % self.stderr) raise Exception('\n'.join(err)) self.return_code = self.process.returncode return self.stdout def shell(cmd): return ShellCmd(cmd)() class Server(object): CMDLINE = '/var/cache/cloud/cmdline' def __init__(self): self.apikey = None self.secretkey = None self.mgmtIp = None self.mgmtPort = None def _get_credentials(self): if not self.apikey or not self.secretkey: with open(self.CMDLINE, 'r') as fd: cmdline = fd.read() for p in cmdline.split(): if 'baremetalnotificationsecuritykey' in p: self.secretkey = p.split("=")[1] if 'baremetalnotificationapikey' in p: self.apikey = p.split("=")[1] if not self.apikey: raise Exception('cannot find baremetalnotificationapikey in %s' % Server.CMDLINE) if not self.secretkey: raise Exception('cannot find baremetalnotificationsecuritykey in %s' % Server.CMDLINE) return self.apikey, self.secretkey def _get_mgmt_ip(self): if not self.mgmtIp: with open(self.CMDLINE, 'r') as fd: cmdline = fd.read() for p in cmdline.split(): if 'host' in p: self.mgmtIp = p.split("=")[1] break if not self.mgmtIp: raise Exception('cannot find host in %s' % Server.CMDLINE) return self.mgmtIp def _get_mgmt_port(self): if not self.mgmtPort: with open(self.CMDLINE, 'r') as fd: cmdline = fd.read() for p in cmdline.split(): if 'port' in p: self.mgmtPort = p.split("=")[1] break if not self.mgmtIp: raise Exception('cannot find port in %s' % Server.CMDLINE) return self.mgmtPort def _make_sign(self, mac): apikey, secretkey = self._get_credentials() reqs = { "apiKey": apikey, "command": 'notifyBaremetalProvisionDone', "mac": mac } request = zip(reqs.keys(), reqs.values()) request.sort(key=lambda x: str.lower(x[0])) hashStr = "&".join(["=".join([str.lower(r[0]), str.lower(urllib.quote_plus(str(r[1]))).replace("+", "%20").replace('=', '%3d')]) for r in request]) sig = urllib.quote_plus(base64.encodestring(hmac.new(secretkey, hashStr, hashlib.sha1).digest()).strip()) return sig def notify_provisioning_done(self, mac): sig = self._make_sign(mac) cmd = 'http://%s:%s/client/api?command=notifyBaremetalProvisionDone&mac=%s&apiKey=%s&signature=%s' % (self._get_mgmt_ip(), self._get_mgmt_port(), mac, self.apikey, sig) shell("curl -X GET '%s'" % cmd) return '' server = None @app.route('/baremetal/provisiondone/', methods=['GET']) def notify_provisioning_done(mac): try: if not is_a_mac(mac): raise "there is an issue with that '%s'. Not a mac?" % mac return server.notify_provisioning_done(mac) except: logger.warn(traceback.format_exc()) return '' def is_a_mac(mac): if re.match("[0-9a-f]{2}([-:]?)[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", mac.lower()): return True else: return False if __name__ == '__main__': server = Server() shell("iptables-save | grep -- '-A INPUT -i eth0 -p tcp -m tcp --dport 10086 -j ACCEPT' > /dev/null || iptables -I INPUT -i eth0 -p tcp -m tcp --dport 10086 -j ACCEPT") app.run(host='0.0.0.0', port=10086)