| #!/usr/bin/python |
| # |
| # 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. |
| # |
| # TODO: Needs cleaning and sanitazation. |
| # |
| import logging |
| import time |
| import re |
| import os.path |
| import paramiko |
| import subprocess |
| import socket |
| import tempfile |
| import logging |
| import logging.handlers |
| |
| from xen.util.xmlrpcclient import ServerProxy |
| from xmlrpclib import Error |
| from xen.xend import XendClient |
| from agent.api.base import Agent |
| from agent.lib.settings import get_api_version |
| from xen.xend import sxp |
| |
| class CloudStack(Agent): |
| """ |
| Cloudstack plugin for OVM3.2.x. |
| """ |
| |
| # exposed services |
| def get_services(self, version=None): |
| return { |
| 'call': call, |
| 'get_vncport': getVncPort, |
| 'exec_domr': domrExec, |
| 'check_domr_port': domrCheckPort, |
| 'check_dom0_port': dom0CheckPort, |
| 'check_domr_ssh': domrCheckSsh, |
| 'check_dom0_ip': dom0CheckIp, |
| # rename to dom0StorageStatusCheck |
| 'check_dom0_storage_health_check': dom0CheckStorageHealthCheck, |
| # dom0StorageStatus |
| 'check_dom0_storage_health': dom0CheckStorageHealth, |
| 'ovs_domr_upload_file': ovsDomrUploadFile, |
| 'ovs_control_interface': ovsControlInterface, |
| 'ovs_mkdirs': ovsMkdirs, |
| 'ovs_check_file': ovsCheckFile, |
| 'ovs_upload_ssh_key': ovsUploadSshKey, |
| 'ovs_upload_file': ovsUploadFile, |
| 'ovs_dom0_stats': ovsDom0Stats, |
| 'ovs_domU_stats': ovsDomUStats, |
| 'get_module_version': getModuleVersion, |
| 'get_ovs_version': ovmVersion, |
| 'ping': ping, |
| # 'patch': ovmCsPatch, |
| # 'ovs_agent_set_ssl': ovsAgentSetSsl, |
| # 'ovs_agent_set_port': ovsAgentSetPort, |
| # 'ovs_restart_agent': ovsRestartAgent, |
| } |
| |
| def getName(self): |
| return self.__class__.__name__ |
| |
| domrPort = 3922 |
| domrKeyFile = os.path.expanduser("~/.ssh/id_rsa.cloud") |
| domrRoot = "root" |
| domrTimeout = 10 |
| |
| """ The logger is here """ |
| def Logger(level=logging.DEBUG): |
| logger = logging.getLogger('cloudstack-agent') |
| logger.setLevel(level) |
| handler = logging.handlers.SysLogHandler(address = '/dev/log') |
| logger.addHandler(handler) |
| return logger |
| |
| # which version are we intended for? |
| def getModuleVersion(): |
| return "0.1" |
| |
| # call test |
| def call(msg): |
| return msg |
| |
| def paramikoOpts(con, keyfile=domrKeyFile): |
| con.load_system_host_keys() |
| con.set_missing_host_key_policy(paramiko.AutoAddPolicy()) |
| privatekeyfile = os.path.expanduser(keyfile) |
| key = paramiko.RSAKey.from_private_key_file(privatekeyfile) |
| return key |
| |
| # execute something on domr |
| def domrExec(host, cmd, timeout=10, username=domrRoot, port=domrPort, keyfile=domrKeyFile): |
| ssh = paramiko.SSHClient() |
| pkey = paramikoOpts(ssh, keyfile) |
| ssh.connect(host, port, username, pkey=pkey, timeout=timeout) |
| ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command(cmd) |
| exit_status = ssh_stdout.channel.recv_exit_status() |
| ssh.close() |
| return { "rc": exit_status, |
| "out": ''.join(ssh_stdout.readlines()), |
| "err": ''.join(ssh_stderr.readlines()) }; |
| |
| # too bad sftp is missing.... Oh no it isn't it's just wrong in the svm config... |
| # root@s-1-VM:/var/cache/cloud# grep sftp /etc/ssh/sshd_config |
| # Subsystem sftp /usr/libexec/openssh/sftp-server |
| # root@s-1-VM:/var/cache/cloud# find / -name sftp-server -type f |
| # /usr/lib/openssh/sftp-server |
| # |
| def domrSftp(host, localfile, remotefile, timeout=10, username=domrRoot, port=domrPort, keyfile=domrKeyFile): |
| try: |
| paramiko.common.logging.basicConfig(level=paramiko.common.DEBUG) |
| ssh = paramiko.SSHClient() |
| ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) |
| pkey = paramikoOpts(ssh, keyfile) |
| ssh.connect(host, port, username, pkey=pkey, timeout=timeout) |
| sftp = ssh.open_sftp() |
| # either: |
| sftp.put(localfile, remotefile) |
| # or: |
| # rf = sftp.open(remotefile, 'w') |
| # rf.write(content) |
| # rf.close() |
| sftp.close() |
| ssh.close() |
| except Exception, e: |
| raise e |
| return True |
| |
| def domrScp(host, localfile, remotefile, timeout=10, username=domrRoot, port=domrPort, keyfile=domrKeyFile): |
| try: |
| target = "%s@%s:%s" % (username, host, remotefile) |
| cmd = ['scp', '-P', str(port), '-q', '-o', 'StrictHostKeyChecking=no', '-i', os.path.expanduser(keyfile), localfile, target] |
| rc = subprocess.call(cmd, shell=False) |
| if rc == 0: |
| return True |
| except Exception, e: |
| raise e |
| return False |
| |
| # check a port on dom0 |
| def dom0CheckPort(ip, port=domrPort, timeout=3): |
| return domrCheckPort(ip, port, timeout=timeout) |
| |
| # check a port on domr |
| def domrCheckPort(ip, port=domrPort, timeout=3): |
| try: |
| s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| s.settimeout(timeout) |
| s.connect((ip, port)) |
| s.close() |
| except: |
| return False |
| return True |
| |
| # check ssh |
| def domrCheckSsh(ip, port=domrPort, timeout=10): |
| x = domrExec(ip, "", port=port, timeout=timeout) |
| if (x.get("rc") == 0): |
| return True |
| return False |
| |
| def grep(file, string): |
| c = 0 |
| for line in open(file): |
| if string in line: |
| c = c + 1 |
| return c |
| |
| def ovmVersion(): |
| path = "/etc/ovs-release" |
| return re.findall("[\d\.]+$", open(path).readline())[0] |
| |
| # fix known bugs.... |
| def ovmCsPatch(version="3.2.1"): |
| path = "/etc/xen/scripts" |
| netcom = "%s/xen-network-common.sh" % path |
| netbr = "%s/linuxbridge/ovs-vlan-bridge" % path |
| func = "setup_bridge_port" |
| # on 3.3.1 this moved to python2.6, but the restart time is already good |
| xendConst = "/usr/lib64/python2.4/site-packages/xen/xend/XendConstants.py" |
| xendRtime = "MINIMUM_RESTART_TIME" |
| netconf = "/etc/sysconfig/network" |
| netzero = "NOZEROCONF" |
| version = ovmVersion() |
| |
| # this bug is present from 3.2.1 till 3.3.2 |
| if grep(netcom, "_%s" % func) == 3 and grep(netbr, "_%s" % func) < 1: |
| _replaceInFile(netbr, func, "_%s" % func, True) |
| |
| # zeroconf is in the way for local loopback, as it introduces a route |
| # on every interface that conflicts with what we want |
| if grep(netconf, "%s" % netzero) == 0: |
| text_file = open("%s" % netconf, "a") |
| text_file.write("%s=no\n" % netzero) |
| text_file.close() |
| else: |
| _replaceInFile(netconf, |
| netzero, |
| "no", |
| False) |
| |
| # this is fixed in 3.3.1 and onwards |
| if version == "3.2.1": |
| if grep(xendConst, "%s = %s" % (xendRtime, 60)) == 1: |
| _replaceInFile(xendConst, |
| "%s = %s" % (xendRtime, 60), |
| "%s = %s" % (xendRtime, 10), |
| True) |
| ovsRestartXend() |
| |
| return True |
| |
| def _replaceInFile(file, orig, set, full=False): |
| replaced = False |
| if os.path.isfile(file): |
| import fileinput |
| for line in fileinput.FileInput(file, inplace=1): |
| line = line.rstrip('\n') |
| if full == False: |
| if re.search("%s=" % orig, line): |
| line = "%s=%s" % (orig, set) |
| replaced = True |
| else: |
| if re.search(orig, line): |
| line = line.replace(orig, set) |
| replaced = True |
| print line |
| return replaced |
| |
| def _ovsIni(setting, change): |
| ini = "/etc/ovs-agent/agent.ini" |
| return _replaceInFile(ini, setting, change) |
| |
| # enable/disable ssl for the agent |
| def ovsAgentSetSsl(state): |
| ena = "disable" |
| if state and state != "disable" and state.lower() != "false": |
| ena = "enable" |
| return _ovsIni("ssl", ena) |
| |
| def ovsAgentSetPort(port): |
| return _ovsIni("port", port) |
| |
| def ovsRestartAgent(): |
| return restartService("ovs-agent") |
| |
| def ovsRestartXend(): |
| return restartService("xend") |
| |
| # replace with popen |
| def restartService(service): |
| command = ['service', service, 'restart'] |
| subprocess.call(command, shell=False) |
| return True |
| |
| # sets the control interface and removes the route net entry |
| def ovsControlInterface(dev, cidr): |
| controlRoute = False |
| command = ['ip route show']; |
| p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) |
| while True: |
| line = p.stdout.readline() |
| if line == '' and p.poll() != None: |
| break |
| if line != '': |
| if re.search("%s" % (cidr), line) and not re.search("%s" % (dev), line): |
| command = ['ip', 'route', 'del', cidr] |
| subprocess.call(command, shell=False) |
| print "removed: %s" % (line) |
| elif re.search("%s" % (cidr), line) and re.search("%s" % (dev), line): |
| controlRoute = True |
| |
| if controlRoute == False: |
| command = ['ip', 'route', 'add', cidr, 'dev', dev]; |
| subprocess.call(command, shell=False) |
| |
| command = ['ifconfig', dev, 'arp'] |
| subprocess.call(command, shell=False) |
| # because OVM creates this and it breaks stuff if we're rebooted sometimes... |
| control = "/etc/sysconfig/network-scripts/ifcfg-%s" % (dev) |
| command = ['rm', control] |
| subprocess.call(command, shell=False) |
| return True |
| |
| def dom0CheckIp(ip): |
| command = ['ip addr show'] |
| p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) |
| while True: |
| line = p.stdout.readline() |
| if line != '': |
| if re.search("%s/" % (ip), line): |
| return True |
| else: |
| break |
| return False |
| |
| def dom0CheckStorageHealthCheck(path, script, guid, timeout, interval): |
| storagehealth="storagehealth.py" |
| path="/opt/cloudstack/bin" |
| running = False |
| started = False |
| c = 0 |
| log = Logger() |
| command = ["pgrep -fl %s | grep -v cloudstack.py" % (storagehealth)] |
| |
| p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) |
| for x in p.stdout: |
| if x: |
| log.debug("%s is running %s" % (storagehealth, x.rstrip('\n'))) |
| running = True |
| c = c + 1 |
| if c < 1: |
| started = True |
| command = ["%s/%s -g %s -t %d -i %d" % (path, storagehealth, guid, timeout, interval)] |
| log.warning("%s started: %s/%s for %s with timeout %d and interval %d" |
| % (storagehealth, path, storagehealth, guid, timeout, interval)) |
| subprocess.call(command, shell=True, close_fds=True) |
| |
| return [running, started] |
| |
| def dom0CheckStorageHealth(path, script, guid, timeout): |
| response = None |
| delay = timeout |
| log = Logger() |
| command = ["%s/%s -g %s -t %s -s" % (path, script, guid, timeout)] |
| p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) |
| p.wait() |
| if p.returncode == 0: |
| log.warning("primary storage is accessible for %s" % (guid)) |
| return True |
| else: |
| log.warning("primary storage NOT is accessible for %s" % (guid)) |
| return False |
| # while True: |
| # line = p.stdout.readline() |
| # if line != '': |
| # if re.search("False", line): |
| # log.debug("primary storage NOT is accessible for %s, %s" % (guid, line)) |
| # return False |
| # else: |
| # break |
| # return True |
| |
| # create a dir if we need it |
| def ovsMkdirs(dir, mode=0700): |
| if not os.path.exists(dir): |
| return os.makedirs(dir, mode) |
| return True |
| |
| # if a file exists, easy |
| def ovsCheckFile(file): |
| if os.path.isfile(file): |
| return True |
| return False |
| |
| def ovsUploadFile(path, filename, content): |
| file = "%s/%s" % (path, filename) |
| try: |
| ovsMkdirs(os.path.expanduser(path)) |
| except Error, v: |
| print "path was already there %s" % path |
| |
| try: |
| text_file = open("%s" % file, "w") |
| text_file.write("%s" % content) |
| text_file.close() |
| except Error, v: |
| print "something went wrong creating %s: %s" % (file, v) |
| return False |
| return True |
| |
| def ovsDomrUploadFile(domr, path, file, content): |
| remotefile = "%s/%s" % (path, file) |
| try: |
| temp = tempfile.NamedTemporaryFile() |
| temp.write(content) |
| temp.flush() |
| # domrSftp(domr, temp.name, remotefile) |
| domrScp(domr, temp.name, remotefile) |
| temp.close |
| except Exception, e: |
| print "problem uploading file %s/%s to %s, %s" % (path, file, domr, e) |
| raise e |
| return True |
| |
| # upload keys |
| def ovsUploadSshKey(keyfile, content): |
| keydir = os.path.expanduser("~/.ssh") |
| return ovsUploadFile(keydir, keyfile, content) |
| |
| # older python, |
| def ovsDom0Stats(bridge): |
| stats = {} |
| stats['cpu'] = "%s" % (100 - float(os.popen("top -b -n 1 | grep Cpu\(s\): | cut -d% -f4|cut -d, -f2").read())) |
| stats['free'] = "%s" % (1048576 * int(os.popen("xm info | grep free_memory | awk '{ print $3 }'").read())) |
| stats['total'] = "%s" % (1048576 * int(os.popen("xm info | grep total_memory | awk '{ print $3 }'").read())) |
| stats['tx'] = os.popen("netstat -in | grep %s | head -1 | awk '{print $4 }'" % bridge).read() |
| stats['rx'] = os.popen("netstat -in | grep %s | head -1 | awk '{print $8 }'" % bridge).read() |
| return stats |
| |
| def getVncPort(domain): |
| port = "0" |
| if re.search("\w-(\d+-)?\d+-VM", domain): |
| server = ServerProxy(XendClient.uri) |
| dom = server.xend.domain(domain, 1) |
| devices = [child for child in sxp.children(dom) |
| if len(child) > 0 and child[0] == "device"] |
| vfbs_sxp = map(lambda x: x[1], [device for device in devices |
| if device[1][0] == "vfb"])[0] |
| loc = [child for child in vfbs_sxp |
| if child[0] == "location"][0][1] |
| listner, port = loc.split(":") |
| else: |
| print "no valid domain: %s" % domain |
| return port |
| |
| def get_child_by_name(exp, childname, default=None): |
| try: |
| return [child for child in sxp.children(exp) |
| if child[0] == childname][0][1] |
| except: |
| return default |
| |
| def ovsDomUStats(domain): |
| _rd_bytes = 0 |
| _wr_bytes = 0 |
| _rd_ops = 0 |
| _wr_ops = 0 |
| _tx_bytes = 0 |
| _rx_bytes = 0 |
| stats = {} |
| server = ServerProxy(XendClient.uri) |
| dominfo = server.xend.domain(domain, 1) |
| domid = get_child_by_name(dominfo, "domid") |
| |
| # vbds |
| devs = server.xend.domain.getDeviceSxprs(domain, 'vbd') |
| devids = [dev[0] for dev in devs] |
| for dev in devids: |
| sys_path = "/sys/devices/%s-%s-%s/statistics" % ("vbd", domid, dev) |
| _rd_bytes += long(open("%s/rd_sect" % sys_path).readline().strip()) |
| _wr_bytes += long(open("%s/wr_sect" % sys_path).readline().strip()) |
| _rd_ops += long(open("%s/rd_req" % sys_path).readline().strip()) |
| _wr_ops += long(open("%s/wr_req" % sys_path).readline().strip()) |
| |
| # vifs |
| devs = server.xend.domain.getDeviceSxprs(domain, 'vif') |
| devids = [dev[0] for dev in devs] |
| for dev in devids: |
| vif = "vif%s.%s" % (domid, dev) |
| sys_path = "/sys/devices/%s-%s-%s/net/%s/statistics" % ("vif", domid, dev, vif) |
| _tx_bytes += long(open("%s/tx_bytes" % sys_path).readline().strip()) |
| _rx_bytes += long(open("%s/rx_bytes" % sys_path).readline().strip()) |
| |
| epoch = time.time() |
| stats['rd_bytes'] = "%s" % (_rd_bytes * 512) |
| stats['wr_bytes'] = "%s" % (_wr_bytes * 512) |
| stats['rd_ops'] = "%s" % (_rd_ops) |
| stats['wr_ops'] = "%s" % (_wr_ops) |
| stats['tx_bytes'] = "%s" % (_tx_bytes) |
| stats['rx_bytes'] = "%s" % (_rx_bytes) |
| stats['cputime'] = "%s" % get_child_by_name(dominfo, "cpu_time") |
| stats['uptime'] = "%s" % (epoch - get_child_by_name(dominfo, "start_time")) |
| stats['vcpus'] = "%s" % get_child_by_name(dominfo, "online_vcpus") |
| return stats |
| |
| def ping(host, count=3): |
| if os.system("ping -c %s %s " % (count, host)) == 0: |
| return True |
| return False |
| |
| # add SystemVM stuff here.... |
| # |
| |
| # |
| # Self deploy and integration, not de-integration |
| # should return False if fails |
| # |
| # install us if we are missing in: |
| # /usr/lib64/python2.4/site-packages/agent/api |
| # and add our hooks in: |
| # /usr/lib64/python2.4/site-packages/agent/target/api.py |
| if __name__ == '__main__': |
| from distutils.sysconfig import get_python_lib |
| from agent.target.api import MODULES |
| from shutil import copyfile |
| import inspect, os, hashlib, getopt, sys |
| |
| # default vars |
| exist = False |
| agentpath = "%s/agent" % (get_python_lib(1)) |
| api = "%s/target/api.py" % (agentpath) |
| modpath = "%s/api" % (agentpath) |
| ssl = "disable" |
| port = 0 |
| exec_sub = "" |
| exec_opts = "" |
| |
| # get options |
| try: |
| opts, args = getopt.getopt(sys.argv[1:], "eosp::", |
| [ 'port=', 'ssl=', 'exec=', 'opts=']) |
| except getopt.GetoptError: |
| print "Available Options: --port=<number>, --ssl=<true|false>, --exec=<method>, --opts=<arg1,arg2..>" |
| sys.exit() |
| |
| for o, a in opts: |
| if o in ('-s', '--ssl'): |
| ssl = a |
| if o in ('-p', '--port'): |
| port = int(a) |
| if o in ('-e', '--exec'): |
| exec_sub = a |
| if o in ('-o', '--opts'): |
| exec_opts = a |
| |
| if exec_sub != "": |
| func = "%s(%s)" % (exec_sub, exec_opts) |
| print "exec: %s" % (func) |
| if exec_opts: |
| opts = exec_opts.split(',') |
| else: |
| opts = "" |
| print locals()[exec_sub](*opts) |
| sys.exit() |
| |
| # check if we're in the modules already |
| cs = CloudStack() |
| for mod in MODULES: |
| if re.search(cs.getName(), "%s" % (mod)): |
| exist = True |
| |
| # if we're not: |
| if not exist: |
| if os.path.isfile(api): |
| import fileinput |
| for line in fileinput.FileInput(api, inplace=1): |
| line = line.rstrip('\n') |
| if re.search("import common", line): |
| line = "%s, cloudstack" % (line) |
| if re.search("MODULES", line): |
| n = cs.getName() |
| line = "%s\n\t%s.%s," % (line, n.lower(), n) |
| print line |
| print "Api inserted, %s in %s" % (cs.getName(), api) |
| else: |
| print "Api missing, %s" % (api) |
| else: |
| print "Api present, %s in %s" % (cs.getName(), api) |
| |
| # either way check our version and install if checksum differs |
| modfile = "%s/%s.py" % (modpath, cs.getName().lower()) |
| me = os.path.abspath(__file__) |
| if os.path.isfile(modfile): |
| if hashlib.md5(open(me).read()).hexdigest() != hashlib.md5(open(modfile).read()).hexdigest(): |
| print "Module copy, %s" % (modfile) |
| copyfile(me, modfile) |
| else: |
| print "Module correct, %s" % (modfile) |
| else: |
| print "Module copy, %s" % (modfile) |
| copyfile(me, modfile) |
| |
| # setup ssl and port |
| if ssl: |
| ovsAgentSetSsl(ssl) |
| if port > 1024: |
| ovsAgentSetPort(port) |
| |
| # restart either way |
| ovmCsPatch() |
| ovsRestartAgent() |