Andrew Bogott has submitted this change and it was merged. Change subject: Initial check-in ......................................................................
Initial check-in Change-Id: I3bda17cb1e671cd0039e91aed8fbb193d50f3a43 --- A .gitreview A README A setup.py A wikistatus/__init__.py A wikistatus/wikistatus.py 5 files changed, 303 insertions(+), 0 deletions(-) Approvals: Andrew Bogott: Verified; Looks good to me, approved diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000..1e74d21 --- /dev/null +++ b/.gitreview @@ -0,0 +1,4 @@ +[gerrit] +host=gerrit.wikimedia.org +port=29418 +project=openstack-wikistatus diff --git a/README b/README new file mode 100644 index 0000000..5f44f33 --- /dev/null +++ b/README @@ -0,0 +1,8 @@ +I'm using 'stdeb' to build debian packages here. To build: + +$ sudo apt-get install python-stdeb +$ python setup.py --command-packages=stdeb.command bdist_deb +$ ls deb_dist/*.deb + +Note that due to file-system weirdness, stddeb errors out on labs +boxes. I'm running it on a VirtualBox VM without any trouble. diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..e4ab5e4 --- /dev/null +++ b/setup.py @@ -0,0 +1,45 @@ +# Copyright 2012 Andrew Bogott for the Wikimedia Foundation +# All Rights Reserved. +# +# Licensed 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 setuptools + +requirements = ["mwclient"] + +setuptools.setup( + name="openstack-wikistatus", + version="2012.6", + author="Wikimedia Foundation", + author_email="[email protected]", + description="Nova plugin that posts live instance status to a wiki page", + long_description="Loooooooooooong description. Super long.", + license="Apache License, Version 2.0", + url="https://github.com/andrewbogott/novawikiplugins", + packages=setuptools.find_packages(exclude=['tests', 'tests.*']), + install_requires=requirements, + tests_require=["nose", "mock"], + test_suite="nose.collector", + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python" + ], + entry_points={ + "nova.plugin": ["plugin=wikistatus.wikistatus:StatusPlugin"] + }, + py_modules=[] +) diff --git a/wikistatus/__init__.py b/wikistatus/__init__.py new file mode 100644 index 0000000..711e39e --- /dev/null +++ b/wikistatus/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed 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 gettext +import logging diff --git a/wikistatus/wikistatus.py b/wikistatus/wikistatus.py new file mode 100644 index 0000000..debd7ef --- /dev/null +++ b/wikistatus/wikistatus.py @@ -0,0 +1,228 @@ +# Copyright 2012 Andrew Bogott for the Wikimedia Foundation +# All Rights Reserved. +# +# Licensed 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 sys + +sys.path.append("/home/andrew/mwclient/") +import mwclient + +from keystoneclient.v2_0 import client as keystoneclient + +from nova import db +from nova import exception +from nova import flags +from nova import image +from nova.openstack.common import log as logging +from nova.openstack.common import cfg +from nova.openstack.common.plugin import plugin +from nova import utils + +LOG = logging.getLogger('nova.plugin.%s' % __name__) + +wiki_opts = [ + cfg.StrOpt('wiki_host', + default='deployment.wikimedia.beta.wmflabs.org', + help='Mediawiki host to receive updates.'), + cfg.StrOpt('wiki_domain', + default='labs', + help='wiki domain to receive updates.'), + cfg.StrOpt('wiki_page_prefix', + default='InstanceStatus_', + help='Created pages will have form <prefix>_<instancename>.'), + cfg.StrOpt('wiki_login', + default='andrewbogott', + help='Account used to edit wiki pages.'), + cfg.StrOpt('wiki_password', + default='bananaphone', + help='Password for wiki_login.'), + cfg.BoolOpt('wiki_use_keystone', + default=True, + help='Indicates whether or not keystone is in use.'), + cfg.StrOpt('wiki_keystone_auth_url', + default='http://127.0.0.1:35357/v2.0', + help='keystone auth url'), + cfg.StrOpt('wiki_keystone_login', + default='admin', + help='keystone admin login'), + cfg.StrOpt('wiki_keystone_password', + default='devstack', + help='keystone admin password'), + cfg.MultiStrOpt('wiki_eventtype_whitelist', + default=['compute.instance.delete.start', + 'compute.instance.delete.end', + 'compute.instance.create.start', + 'compute.instance.create.end', + 'compute.instance.rebuild.start', + 'compute.instance.rebuild.end', + 'compute.instance.resize.start', + 'compute.instance.resize.end', + 'compute.instance.suspend', + 'compute.instance.resume', + 'compute.instance.exists'], + help='Event types to always handle.'), + cfg.MultiStrOpt('wiki_eventtype_blacklist', + default=[], + help='Event types to always ignore.' + 'In the event of a conflict, ' + 'this overrides the whitelist.')] + + +FLAGS = flags.FLAGS +FLAGS.register_opts(wiki_opts) + + +class WikiStatus(object): + """Notifier class which posts instance info to a wiki page. + + Activate with something like this: + + --notification_driver = nova.notifier.list_notifier + --list_notifier_drivers = nova.wikistatus.WikiStatus + + Or inject via the plugin, below. + """ + + RawTemplateFields = ['created_at', + 'disk_gb', + 'display_name', + 'instance_id', + 'instance_type', + 'launched_at', + 'memory_mb', + 'state', + 'state_description'] + + def __init__(self): + self.host = FLAGS.wiki_host + self.site = None + self.kclient = {} + self.tenant_manager = {} + self.user_manager = {} + self._wiki_logged_in = False + self._image_service = image.glance.get_default_image_service() + + def _wiki_login(self): + if not self._wiki_logged_in: + if not self.site: + self.site = mwclient.Site(self.host, + retry_timeout=5, + max_retries=2) + if self.site: + self.site.login(FLAGS.wiki_login, FLAGS.wiki_password, + domain=FLAGS.wiki_domain) + self._wiki_logged_in = True + else: + LOG.warning("Unable to reach %s. We'll keep trying, " + "but pages will be out of sync in the meantime.") + + def _keystone_login(self, tenant_id, ctxt): + if tenant_id not in self.kclient: + self.kclient[tenant_id] = keystoneclient.Client( + token='devstack', + username=FLAGS.wiki_keystone_login, + password=FLAGS.wiki_keystone_password, + tenant_id=tenant_id, + endpoint=FLAGS.wiki_keystone_auth_url) + + self.tenant_manager[tenant_id] = self.kclient[tenant_id].tenants + self.user_manager[tenant_id] = self.kclient[tenant_id].users + + return self.kclient[tenant_id] + + def notify(self, ctxt, message): + event_type = message.get('event_type') + if event_type in FLAGS.wiki_eventtype_blacklist: + return + if event_type not in FLAGS.wiki_eventtype_whitelist: + LOG.debug("Ignoring message type %s" % event_type) + return + + payload = message['payload'] + instance = payload['instance_id'] + instance_name = payload['display_name'] + + pagename = "%s%s" % (FLAGS.wiki_page_prefix, instance_name) + LOG.debug("wikistatus: Writing instance info" + " to page http://%s/wiki/%s" % + (self.host, pagename)) + + if event_type == 'compute.instance.delete.end': + page_string = _("This instance has been deleted.") + else: + template_param_dict = {} + for field in self.RawTemplateFields: + template_param_dict[field] = payload[field] + + tenant_id = payload['tenant_id'] + if (FLAGS.wiki_use_keystone and self._keystone_login(tenant_id, + ctxt)): + tenant_obj = self.tenant_manager[tenant_id].get(tenant_id) + user_obj = self.user_manager[tenant_id].get(payload['user_id']) + tenant_name = tenant_obj.name + user_name = user_obj.name + template_param_dict['tenant'] = tenant_name + template_param_dict['username'] = user_name + + inst = db.instance_get_by_uuid(ctxt, payload['instance_id']) + + simple_id = inst.id + template_param_dict['cpu_count'] = inst.vcpus + template_param_dict['disk_gb_current'] = inst.ephemeral_gb + template_param_dict['host'] = inst.host + template_param_dict['reservation_id'] = inst.reservation_id + template_param_dict['availability_zone'] = inst.availability_zone + template_param_dict['original_host'] = inst.launched_on + template_param_dict['public_ip'] = inst.access_ip_v4 + + try: + fixed_ips = db.fixed_ip_get_by_instance(ctxt, + payload['instance_id']) + except exception.FixedIpNotFoundForInstance: + fixed_ips = [] + ips = [ip.address for ip in fixed_ips] + template_param_dict['private_ip'] = ','.join(ips) + + sec_groups = db.security_group_get_by_instance(ctxt, simple_id) + grps = [grp.name for grp in sec_groups] + template_param_dict['security_group'] = ','.join(grps) + + image = self._image_service.show(ctxt, inst.image_ref) + image_name = image.get('name', inst.image_ref) + template_param_dict['image_name'] = image_name + + fields_string = "" + for key in template_param_dict: + fields_string += "\n|%s=%s" % (key, template_param_dict[key]) + + page_string = "{{InstanceStatus%s}}" % fields_string + + self._wiki_login() + page = self.site.Pages[pagename] + try: + page.edit() + page.save(page_string, "Auto update of instance info.") + except (mwclient.errors.InsufficientPermission, + mwclient.errors.LoginError): + LOG.debug("Failed to update wiki page..." + " trying to re-login next time.") + self._wiki_logged_in = False + + +class StatusPlugin(plugin.Plugin): + + def __init__(self, service_name): + super(StatusPlugin, self).__init__(service_name) + statusNotifier = WikiStatus() + self.service_name = service_name + self._add_notifier(statusNotifier) -- To view, visit https://gerrit.wikimedia.org/r/64624 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I3bda17cb1e671cd0039e91aed8fbb193d50f3a43 Gerrit-PatchSet: 2 Gerrit-Project: openstack-wikistatus Gerrit-Branch: master Gerrit-Owner: Andrew Bogott <[email protected]> Gerrit-Reviewer: Andrew Bogott <[email protected]> _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
