Skip to content

Commit

Permalink
Merge branch 'archipel-agent' of https://github.com/everplays/Archipel
Browse files Browse the repository at this point in the history
…into archipel-vmguest

Conflicts:
	ArchipelAgent/archipel-core/archipelcore/archipelEntity.py
  • Loading branch information
primalmotion committed Jul 10, 2012
2 parents 495e383 + 7160233 commit 4f6f999
Show file tree
Hide file tree
Showing 17 changed files with 2,438 additions and 4 deletions.
661 changes: 661 additions & 0 deletions ArchipelAgent/archipel-agent-virtualmachine-agent/LICENSE

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions ArchipelAgent/archipel-agent-virtualmachine-agent/MANIFEST.in
@@ -0,0 +1,2 @@
include LICENSE setup.py setup.cfg
recursive-include archipelagentvirtualmachineagent *
@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
#
# __init__.py
#
# Copyright (C) 2012 parspooyesh <everplays@gmail.com>
# This file is part of ArchipelProject
# http://archipelproject.org
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import virtualmachineagent

def make_archipel_plugin(configuration, entity, group):
"""
This function is the plugin factory. It will be called by the object you want
to be plugged in. It must return a list whit at least on dictionary containing
a key for the the plugin informations, and a key for the plugin object.
@type configuration: Config Object
@param configuration: the general configuration object
@type entity: L{TNArchipelEntity}
@param entity: the entity that has load the plugin
@type group: string
@param group: the entry point group name in which the plugin has been loaded
@rtype: array
@return: array of dictionary containing the plugins informations and objects
"""
return [{"info": virtualmachineagent.TNVirtualMachineAgent.plugin_info(),
"plugin": virtualmachineagent.TNVirtualMachineAgent(configuration, entity, group)}]

def version():
"""
This function can be called runarchipel -v in order to get the version of the
installed plugin. You only should have to change the egg name.
@rtype: tupple
@return: tupple containing the package name and the version
"""
import pkg_resources
return (__name__, pkg_resources.get_distribution("archipel-agent-virtualmachine-guest").version, [virtualmachineagent.TNVirtualMachineAgent.plugin_info()])

@@ -0,0 +1,211 @@
# -*- coding: utf-8 -*-
#
# virtualmachineagent.py
#
# Copyright (C) 2012 parspooyesh <everplays@gmail.com>
# This file is part of ArchipelProject
# http://archipelproject.org
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import xmpp
from archipelcore.archipelPlugin import TNArchipelPlugin
from archipelcore.utils import build_error_iq

ARCHIPEL_ERROR_CODE_VIRTUALMACHINEAGENT_EXEC = -10001

# Namespace
ARCHIPEL_NS_GUEST_CONTROL = "archipel:guest:control"

class TNVirtualMachineAgent(TNArchipelPlugin):
"""
this module enables user to send commands to archipelguest running on guest os
"""
def __init__(self, configuration, entity, entry_point_group):
"""
Initialize the plugin.
@type configuration: Configuration object
@param configuration: the configuration
@type entity: L{TNArchipelEntity}
@param entity: the entity that owns the plugin
@type entry_point_group: string
@param entry_point_group: the group name of plugin entry_point
"""
TNArchipelPlugin.__init__(self, configuration=configuration, entity=entity, entry_point_group=entry_point_group)
self.entity.add_vm_definition_hook(self.add_net_switch_to_definition)

@staticmethod
def plugin_info():
"""
Return informations about the plugin.
@rtype: dict
@return: dictionary contaning plugin informations
"""
return { "common-name" : "Virtual Machine Agent",
"identifier" : "virtualmachineguestagent",
"configuration-section" : None,
"configuration-tokens" : []}

def register_handlers(self):
"""
lets register our stanza handlers
"""
TNArchipelPlugin.register_handlers(self)
self.entity.xmppclient.RegisterHandler('message', self.process_message, typ="chat")
self.entity.xmppclient.RegisterHandler('iq', self.process_iq, ns=ARCHIPEL_NS_GUEST_CONTROL)

def unregister_handlers(self):
"""
hmm, seems we have to unregister our handlers
"""
TNArchipelEntity.unregister_handlers(self)
self.entity.xmppclient.UnregisterHandler('message', self.process_message, typ="chat")
self.entity.xmppclient.UnregisterHandler('iq', self.process_iq, ns=ARCHIPEL_NS_GUEST_CONTROL)

def execute(self, command, executor):
"""
generates an Iq get to agent running on guest
@type command: String
@param command: the command that should be executed on guest machine
@type executor: String
@param executor: the jid that sent the command (will be used for sending result back)
@rtype: xmpp.protocol.Iq
@return: generated Iq stanza
"""
to = xmpp.JID(self.entity.uuid+'-agent@'+self.entity.jid.getDomain()+'/guestagent')
iq = xmpp.protocol.Iq(typ='get', to=to)
iq.setQueryNS(ARCHIPEL_NS_GUEST_CONTROL);
query = iq.getTag("query");
archipel = query.addChild('archipel', attrs={
"executor": executor,
"action": "exec"});
archipel.addData(command)
return iq

def add_net_switch_to_definition(self, sender, xmldesc):
"""
adds network switches if GUEST.enabled configuration is true
@type sender: xmpp.JID
@param sender: the jid that edited definition
@type xmldesc: xmpp.Node
@param xmldesc: xml definition that is going to be sent to libvirt
@rtype: xmpp.Node
@return: xml definition
"""
shouldBeAdded = False
name = xmldesc.getTag('name').getData()
hostname = 'user,hostname=%s.%s' % (name, self.entity.jid.getDomain())
# check if we already have added switch
commandline = xmldesc.getTag('commandline', namespace='qemu')
if commandline == None:
# add commandline tag, if we don't have any
shouldBeAdded = True
commandline = xmldesc.addChild(name='qemu:commandline', attrs={
"xmlns:qemu": 'http://libvirt.org/schemas/domain/qemu/1.0'})
else:
# if we have commandline tag, check for args to be like:
# 0: -net
# 1: nic,model=virtio,addr=0xf,macaddr=92:42:ce:df:64:61
# 2: -net
# 3: user,hostname=...
hasSwitches = 0
for arg in commandline.getTags('arg', namespace='qemu'):
if arg.getAttr('value') == '-net' and (hasSwitches == 0 or hasSwitches == 2):
hasSwitches += 1
continue
if hasSwitches == 1:
if arg.getAttr('value')=='nic,model=virtio,addr=0xf,macaddr=92:42:ce:df:64:61':
hasSwitches += 1
continue
if hasSwitches == 3:
if arg.getAttr('value')==hostname:
hasSwitches += 1
break
hasSwitches = 0
if hasSwitches < 4:
shouldBeAdded = True
if shouldBeAdded:
commandline.addChild(name='qemu:arg', attrs={'value': '-net'})
commandline.addChild(name='qemu:arg', attrs={'value': 'nic,model=virtio,addr=0xf,macaddr=92:42:ce:df:64:61'})
commandline.addChild(name='qemu:arg', attrs={'value': '-net'})
commandline.addChild(name='qemu:arg', attrs={'value': hostname })
return xmldesc

def process_iq(self, conn, iq):
"""
processes iq messages with archipel:guest:control
@type conn: xmpp.Dispatcher
@param conn: instance of connection that sent message
@type iq: xmpp.Protocol.Iq
@param iq: received Iq stanza
"""
reply = None
action = self.entity.check_acp(conn, iq)

if action == "exec":
reply = self.iq_exec(iq)
if reply:
conn.send(reply)
raise xmpp.NodeProcessed

def iq_exec(self, iq):
"""
processes iq with exec type and returns the stanza that should be sent
@type id: xmpp.Protocol.Iq
@param id: received iq
@rtype: xmpp.Protocol
@return: the stanza that should be sent or None if we've not processes the stanza
"""
# if we've received iq from agent running in guest os it has to be result of
# a executed command, so tunnel result back to user as message
# TODO: if we received an Iq from agent running in guest and jid has permission
# to send us Iq, we should tunnel his/her command to agent and sent it back as Iq
# when we received result Iq
reply = None
try:
if str(iq.getFrom()).lower() == (self.entity.uuid+"-agent@"+self.entity.jid.getDomain()+"/guestagent").lower():
archipel = iq.getTag("query").getTag("archipel")
reply = xmpp.protocol.Message(archipel.getAttr('executor'), archipel.getData())
except Exception as ex:
reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_VIRTUALMACHINEAGENT_EXEC)
return reply

def process_message(self, conn, msg):
"""
processes messages that start with !exec as command that should be run on guest os
@type conn: xmpp.Dispatcher
@param conn: instance of connection that sent message
@type msg: xmpp.Protocol.Message
@param msg: received message stanza
"""
body = str(msg.getBody())
if body.find("!exec") == 0 and self.entity.permission_center.check_permission(str(msg.getFrom().getStripped()), "message"):
runIq = self.msg_exec(msg)
self.entity.log.info('sending: '+str(runIq))
conn.send(runIq)
raise xmpp.NodeProcessed

def msg_exec(self, msg):
"""
makes an Iq stanza to agent running on guest to run the command
@type msg: xmpp.Protocol.Message
@param msg: message that starts with !exec
@rtype: xmpp.Protocol
@return: the stanza that must be sent
"""
body = msg.getBody()
command = body.replace('!exec', '').strip()
executor = msg.getFrom()
return self.execute(command, executor)

3 changes: 3 additions & 0 deletions ArchipelAgent/archipel-agent-virtualmachine-agent/setup.cfg
@@ -0,0 +1,3 @@
[egg_info]
tag_build = beta
tag_svn_revision = true
83 changes: 83 additions & 0 deletions ArchipelAgent/archipel-agent-virtualmachine-agent/setup.py
@@ -0,0 +1,83 @@
#
# setup.py
#
# Copyright (C) 2012 parspooyesh <everplays@gmail.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from setuptools import setup, find_packages

VERSION = '0.5.0'

AUTHOR = 'Behrooz Shabani'
MAIL = 'everplays@gmail.com'
URL = 'http://archipelproject.org'
LICENSE = 'AGPL'
NAME = 'archipel-agent-virtualmachine-guest'
SHORTDESCRIPTION = "This module enables user to execute commands on guest os"
LONGDESCRIPTION = "This module works as a tunnel between guest-agent running on \
guest os and user (or other modules). module transforms every messages starting with \
!exec to guest-agent (using xmpp) to run on guest os, so if user runs !exec ls the ls \
command will run on guest os and result of it will be sent back to user as message."
ENTRY_POINTS = { 'archipel.plugin.virtualmachine' : [
'factory=archipelvirtualmachineagent:make_archipel_plugin'],
'archipel.plugin' : [
'factory=archipelvirtualmachineagent:version']}

RPM_REQUIRED_DEPS = "archipel-core"

## HACK FOR DEPS IN RPMS
from setuptools.command.bdist_rpm import bdist_rpm
def custom_make_spec_file(self):
spec = self._original_make_spec_file()
lineDescription = "%description"
spec.insert(spec.index(lineDescription) - 1, "requires: %s" % RPM_REQUIRED_DEPS)
return spec
bdist_rpm._original_make_spec_file = bdist_rpm._make_spec_file
bdist_rpm._make_spec_file = custom_make_spec_file
## END OF HACK


setup(name=NAME,
version=VERSION,
description=SHORTDESCRIPTION,
long_description=LONGDESCRIPTION,
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Console',
'Environment :: No Input/Output (Daemon)',
'Intended Audience :: Developers',
'Intended Audience :: Education',
'Intended Audience :: End Users/Desktop',
'Intended Audience :: Science/Research',
'Intended Audience :: System Administrators',
'Intended Audience :: Telecommunications Industry',
'License :: OSI Approved :: GNU Affero General Public License v3',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Topic :: Internet',
'Topic :: System :: Emulators',
'Topic :: System :: Operating System'],
keywords='archipel, virtualization, libvirt, orchestration',
author=AUTHOR,
author_email=MAIL,
url=URL,
license=LICENSE,
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
include_package_data=True,
zip_safe=False,
install_requires=[
"archipel-core>=0.5.0beta"
],
entry_points=ENTRY_POINTS
)
Expand Up @@ -231,6 +231,7 @@ vnc = True
xmppserver = True
platformrequest = False
vmparking = True
virtualmachineguestagent = False


###############################################################################
Expand Down Expand Up @@ -479,4 +480,5 @@ libvirt_nw_filters_path = /etc/libvirt/nwfilter
[VMPARKING]

# path for shared parking database file
database = %(archipel_folder_data)s/shared_parking.sqlite3
database = %(archipel_folder_data)s/shared_parking.sqlite3

6 changes: 3 additions & 3 deletions ArchipelAgent/archipel-core/archipelcore/archipelEntity.py
Expand Up @@ -476,14 +476,14 @@ def register_handlers(self):
TNRosterQueryableEntity.register_handlers(self)
if isinstance(self, TNFileTransferCapableEntity):
TNFileTransferCapableEntity.register_handlers(self)
for plugin in self.plugins:
self.log.info("PLUGIN: registering stanza handler for plugin %s" % plugin["info"]["identifier"])
plugin["plugin"].register_handlers()
self.xmppclient.RegisterHandler('presence', self.process_presence)
self.xmppclient.RegisterHandler('message', self.process_message, typ="chat")
self.xmppclient.RegisterHandler('iq', self.process_permission_iq, ns=ARCHIPEL_NS_PERMISSIONS)
self.xmppclient.RegisterHandler('iq', self.process_subscription_iq, ns=ARCHIPEL_NS_SUBSCRIPTION)
self.xmppclient.RegisterHandler('iq', self.process_ping_iq, typ="get", ns="urn:xmpp:ping")
for plugin in self.plugins:
self.log.info("PLUGIN: registering stanza handler for plugin %s" % plugin["info"]["identifier"])
plugin["plugin"].register_handlers()
self.log.info("handlers registred")

def unregister_handlers(self):
Expand Down

0 comments on commit 4f6f999

Please sign in to comment.