Rebase on charmhelpers for havana
This commit is contained in:
parent
2b0ec42777
commit
478087ada3
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/make
|
||||
|
||||
lint:
|
||||
@flake8 --exclude hooks/charmhelpers hooks
|
||||
@charm proof
|
||||
|
||||
sync:
|
||||
@charm-helper-sync -c charm-helpers.yaml
|
||||
|
||||
test:
|
||||
@$(PYTHON) /usr/bin/nosetests --nologcapture --with-coverage unit_tests
|
|
@ -0,0 +1,6 @@
|
|||
branch: lp:~openstack-charmers/charm-helpers/to_upstream
|
||||
destination: hooks/charmhelpers
|
||||
include:
|
||||
- core
|
||||
- fetch
|
||||
- contrib.openstack
|
|
@ -1 +1 @@
|
|||
hooks.py
|
||||
ceilometer_hooks.py
|
|
@ -0,0 +1,97 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import sys
|
||||
import os
|
||||
import ceilometer_utils
|
||||
import socket.gethostname as get_host_name
|
||||
|
||||
from charmhelpers.fetch import (
|
||||
apt_install, filter_installed_packages,
|
||||
apt_update
|
||||
)
|
||||
|
||||
from charmhelpers.core.hookenv import (
|
||||
config,
|
||||
relation_ids,
|
||||
related_units,
|
||||
relation_get,
|
||||
Hooks, UnregisteredHookError,
|
||||
log
|
||||
)
|
||||
from charmhelpers.core.host import (
|
||||
service_restart
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.openstack.utils import configure_installation_source
|
||||
|
||||
hooks = Hooks()
|
||||
|
||||
|
||||
@hooks.hook()
|
||||
def install():
|
||||
configure_installation_source(config('openstack-origin'))
|
||||
apt_update(fatal=True)
|
||||
apt_install(filter_installed_packages(ceilometer_utils.CEILOMETER_AGENT_PACKAGES),
|
||||
fatal=True)
|
||||
|
||||
# TODO(jamespage): Locally scoped relation for nova and others
|
||||
#ceilometer_utils.modify_config_file(ceilometer_utils.NOVA_CONF,
|
||||
# ceilometer_utils.NOVA_SETTINGS)
|
||||
|
||||
|
||||
def get_conf():
|
||||
for relid in relation_ids('ceilometer-service'):
|
||||
for unit in related_units(relid):
|
||||
conf = {
|
||||
"rabbit_host": relation_get('rabbit_host', unit, relid),
|
||||
"rabbit_virtual_host": ceilometer_utils.RABBIT_VHOST,
|
||||
"rabbit_userid": ceilometer_utils.RABBIT_USER,
|
||||
"rabbit_password": relation_get('rabbit_password',
|
||||
unit, relid),
|
||||
"keystone_os_username": relation_get('keystone_os_username',
|
||||
unit, relid),
|
||||
"keystone_os_password": relation_get('keystone_os_password',
|
||||
unit, relid),
|
||||
"keystone_os_tenant": relation_get('keystone_os_tenant',
|
||||
unit, relid),
|
||||
"keystone_host": relation_get('keystone_host', unit, relid),
|
||||
"keystone_port": relation_get('keystone_port', unit, relid),
|
||||
"metering_secret": relation_get('metering_secret', unit, relid)
|
||||
}
|
||||
if None not in conf.itervalues():
|
||||
return conf
|
||||
return None
|
||||
|
||||
|
||||
def render_ceilometer_conf(context):
|
||||
if (context and os.path.exists(ceilometer_utils.CEILOMETER_CONF)):
|
||||
context['service_port'] = ceilometer_utils.CEILOMETER_PORT
|
||||
context['ceilometer_host'] = get_host_name()
|
||||
|
||||
with open(ceilometer_utils.CEILOMETER_CONF, "w") as conf:
|
||||
conf.write(ceilometer_utils.render_template(
|
||||
os.path.basename(ceilometer_utils.CEILOMETER_CONF), context))
|
||||
|
||||
for svc in ceilometer_utils.CEILOMETER_COMPUTE_SERVICES:
|
||||
service_restart(svc)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@hooks.hook("ceilometer-service-relation-changed")
|
||||
def ceilometer_changed():
|
||||
# check if we have rabbit and keystone already set
|
||||
context = get_conf()
|
||||
if context:
|
||||
render_ceilometer_conf(context)
|
||||
else:
|
||||
# still waiting
|
||||
log("ceilometer: rabbit and keystone "
|
||||
"credentials not yet received from peer.")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
hooks.execute(sys.argv)
|
||||
except UnregisteredHookError as e:
|
||||
log('Unknown hook {} - skipping.'.format(e))
|
|
@ -1,28 +1,36 @@
|
|||
import os
|
||||
import uuid
|
||||
import ConfigParser
|
||||
from charmhelpers.fetch import apt_install as install
|
||||
|
||||
RABBIT_USER = "ceilometer"
|
||||
RABBIT_VHOST = "ceilometer"
|
||||
CEILOMETER_CONF = "/etc/ceilometer/ceilometer.conf"
|
||||
|
||||
SHARED_SECRET = "/etc/ceilometer/secret.txt"
|
||||
CEILOMETER_SERVICES = ['ceilometer-agent-central', 'ceilometer-collector',
|
||||
'ceilometer-api']
|
||||
CEILOMETER_SERVICES = [
|
||||
'ceilometer-agent-central', 'ceilometer-collector',
|
||||
'ceilometer-api'
|
||||
]
|
||||
CEILOMETER_DB = "ceilometer"
|
||||
CEILOMETER_SERVICE = "ceilometer"
|
||||
CEILOMETER_COMPUTE_SERVICES = ['ceilometer-agent-compute']
|
||||
CEILOMETER_PACKAGES = ['python-ceilometer', 'ceilometer-common',
|
||||
'ceilometer-agent-central', 'ceilometer-collector', 'ceilometer-api']
|
||||
CEILOMETER_AGENT_PACKAGES = ['python-ceilometer', 'ceilometer-common',
|
||||
'ceilometer-agent-compute']
|
||||
CEILOMETER_PACKAGES = [
|
||||
'python-ceilometer', 'ceilometer-common',
|
||||
'ceilometer-agent-central', 'ceilometer-collector', 'ceilometer-api'
|
||||
]
|
||||
CEILOMETER_AGENT_PACKAGES = [
|
||||
'python-ceilometer', 'ceilometer-common',
|
||||
'ceilometer-agent-compute'
|
||||
]
|
||||
CEILOMETER_PORT = 8777
|
||||
CEILOMETER_ROLE = "ResellerAdmin"
|
||||
|
||||
NOVA_CONF = "/etc/nova/nova.conf"
|
||||
NOVA_SETTINGS = [
|
||||
('DEFAULT', 'instance_usage_audit', 'True'),
|
||||
('DEFAULT', 'instance_usage_audit_period', 'hour'),
|
||||
('DEFAULT', 'notification_driver', 'ceilometer.compute.nova_notifier')]
|
||||
('DEFAULT', 'notification_driver', 'ceilometer.compute.nova_notifier')
|
||||
]
|
||||
|
||||
|
||||
def get_shared_secret():
|
||||
|
@ -37,28 +45,17 @@ def get_shared_secret():
|
|||
return secret
|
||||
|
||||
|
||||
def modify_config_file(nova_conf, values):
|
||||
# check if config file exists
|
||||
if not os.path.exists(nova_conf):
|
||||
juju_log('ERROR', 'nova config file must exist at this point')
|
||||
sys.exit(1)
|
||||
TEMPLATES_DIR = 'templates'
|
||||
|
||||
try:
|
||||
config = ConfigParser.ConfigParser()
|
||||
with open(nova_conf, "r") as f:
|
||||
config.readfp(f)
|
||||
try:
|
||||
import jinja2
|
||||
except ImportError:
|
||||
install(['python-jinja2'])
|
||||
import jinja2
|
||||
|
||||
# add needed config lines - tuple with section,key,value
|
||||
for value in values:
|
||||
config.set(value[0], value[1], value[2])
|
||||
|
||||
with open(nova_conf, "w") as f:
|
||||
config.write(f)
|
||||
|
||||
# add line at the end, it's a dupe and configparser doesn't handle it
|
||||
with open(nova_conf, "a") as f:
|
||||
f.write("notification_driver=nova.openstack.common.notifier.rabbit_notifier\n")
|
||||
|
||||
except IOError as e:
|
||||
juju_log('ERROR', 'Error updating nova config file')
|
||||
sys.exit(1)
|
||||
def render_template(template_name, context, template_dir=TEMPLATES_DIR):
|
||||
templates = \
|
||||
jinja2.Environment(loader=jinja2.FileSystemLoader(template_dir))
|
||||
template = templates.get_template(template_name)
|
||||
return template.render(context)
|
||||
|
|
|
@ -0,0 +1,433 @@
|
|||
import os
|
||||
|
||||
from base64 import b64decode
|
||||
|
||||
from subprocess import (
|
||||
check_call
|
||||
)
|
||||
|
||||
|
||||
from charmhelpers.fetch import (
|
||||
apt_install,
|
||||
filter_installed_packages,
|
||||
)
|
||||
|
||||
from charmhelpers.core.hookenv import (
|
||||
config,
|
||||
local_unit,
|
||||
log,
|
||||
relation_get,
|
||||
relation_ids,
|
||||
related_units,
|
||||
unit_get,
|
||||
unit_private_ip,
|
||||
WARNING,
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.hahelpers.cluster import (
|
||||
determine_api_port,
|
||||
determine_haproxy_port,
|
||||
https,
|
||||
is_clustered,
|
||||
peer_units,
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.hahelpers.apache import (
|
||||
get_cert,
|
||||
get_ca_cert,
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.openstack.neutron import (
|
||||
neutron_plugin_attribute,
|
||||
)
|
||||
|
||||
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
|
||||
|
||||
|
||||
class OSContextError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def ensure_packages(packages):
|
||||
'''Install but do not upgrade required plugin packages'''
|
||||
required = filter_installed_packages(packages)
|
||||
if required:
|
||||
apt_install(required, fatal=True)
|
||||
|
||||
|
||||
def context_complete(ctxt):
|
||||
_missing = []
|
||||
for k, v in ctxt.iteritems():
|
||||
if v is None or v == '':
|
||||
_missing.append(k)
|
||||
if _missing:
|
||||
log('Missing required data: %s' % ' '.join(_missing), level='INFO')
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class OSContextGenerator(object):
|
||||
interfaces = []
|
||||
|
||||
def __call__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class SharedDBContext(OSContextGenerator):
|
||||
interfaces = ['shared-db']
|
||||
|
||||
def __init__(self, database=None, user=None, relation_prefix=None):
|
||||
'''
|
||||
Allows inspecting relation for settings prefixed with relation_prefix.
|
||||
This is useful for parsing access for multiple databases returned via
|
||||
the shared-db interface (eg, nova_password, quantum_password)
|
||||
'''
|
||||
self.relation_prefix = relation_prefix
|
||||
self.database = database
|
||||
self.user = user
|
||||
|
||||
def __call__(self):
|
||||
self.database = self.database or config('database')
|
||||
self.user = self.user or config('database-user')
|
||||
if None in [self.database, self.user]:
|
||||
log('Could not generate shared_db context. '
|
||||
'Missing required charm config options. '
|
||||
'(database name and user)')
|
||||
raise OSContextError
|
||||
ctxt = {}
|
||||
|
||||
password_setting = 'password'
|
||||
if self.relation_prefix:
|
||||
password_setting = self.relation_prefix + '_password'
|
||||
|
||||
for rid in relation_ids('shared-db'):
|
||||
for unit in related_units(rid):
|
||||
passwd = relation_get(password_setting, rid=rid, unit=unit)
|
||||
ctxt = {
|
||||
'database_host': relation_get('db_host', rid=rid,
|
||||
unit=unit),
|
||||
'database': self.database,
|
||||
'database_user': self.user,
|
||||
'database_password': passwd,
|
||||
}
|
||||
if context_complete(ctxt):
|
||||
return ctxt
|
||||
return {}
|
||||
|
||||
|
||||
class IdentityServiceContext(OSContextGenerator):
|
||||
interfaces = ['identity-service']
|
||||
|
||||
def __call__(self):
|
||||
log('Generating template context for identity-service')
|
||||
ctxt = {}
|
||||
|
||||
for rid in relation_ids('identity-service'):
|
||||
for unit in related_units(rid):
|
||||
ctxt = {
|
||||
'service_port': relation_get('service_port', rid=rid,
|
||||
unit=unit),
|
||||
'service_host': relation_get('service_host', rid=rid,
|
||||
unit=unit),
|
||||
'auth_host': relation_get('auth_host', rid=rid, unit=unit),
|
||||
'auth_port': relation_get('auth_port', rid=rid, unit=unit),
|
||||
'admin_tenant_name': relation_get('service_tenant',
|
||||
rid=rid, unit=unit),
|
||||
'admin_user': relation_get('service_username', rid=rid,
|
||||
unit=unit),
|
||||
'admin_password': relation_get('service_password', rid=rid,
|
||||
unit=unit),
|
||||
# XXX: Hard-coded http.
|
||||
'service_protocol': 'http',
|
||||
'auth_protocol': 'http',
|
||||
}
|
||||
if context_complete(ctxt):
|
||||
return ctxt
|
||||
return {}
|
||||
|
||||
|
||||
class AMQPContext(OSContextGenerator):
|
||||
interfaces = ['amqp']
|
||||
|
||||
def __call__(self):
|
||||
log('Generating template context for amqp')
|
||||
conf = config()
|
||||
try:
|
||||
username = conf['rabbit-user']
|
||||
vhost = conf['rabbit-vhost']
|
||||
except KeyError as e:
|
||||
log('Could not generate shared_db context. '
|
||||
'Missing required charm config options: %s.' % e)
|
||||
raise OSContextError
|
||||
|
||||
ctxt = {}
|
||||
for rid in relation_ids('amqp'):
|
||||
for unit in related_units(rid):
|
||||
if relation_get('clustered', rid=rid, unit=unit):
|
||||
ctxt['clustered'] = True
|
||||
ctxt['rabbitmq_host'] = relation_get('vip', rid=rid,
|
||||
unit=unit)
|
||||
else:
|
||||
ctxt['rabbitmq_host'] = relation_get('private-address',
|
||||
rid=rid, unit=unit)
|
||||
ctxt.update({
|
||||
'rabbitmq_user': username,
|
||||
'rabbitmq_password': relation_get('password', rid=rid,
|
||||
unit=unit),
|
||||
'rabbitmq_virtual_host': vhost,
|
||||
})
|
||||
if context_complete(ctxt):
|
||||
# Sufficient information found = break out!
|
||||
break
|
||||
# Used for active/active rabbitmq >= grizzly
|
||||
ctxt['rabbitmq_hosts'] = []
|
||||
for unit in related_units(rid):
|
||||
ctxt['rabbitmq_hosts'].append(relation_get('private-address',
|
||||
rid=rid, unit=unit))
|
||||
if not context_complete(ctxt):
|
||||
return {}
|
||||
else:
|
||||
return ctxt
|
||||
|
||||
|
||||
class CephContext(OSContextGenerator):
|
||||
interfaces = ['ceph']
|
||||
|
||||
def __call__(self):
|
||||
'''This generates context for /etc/ceph/ceph.conf templates'''
|
||||
if not relation_ids('ceph'):
|
||||
return {}
|
||||
log('Generating template context for ceph')
|
||||
mon_hosts = []
|
||||
auth = None
|
||||
key = None
|
||||
for rid in relation_ids('ceph'):
|
||||
for unit in related_units(rid):
|
||||
mon_hosts.append(relation_get('private-address', rid=rid,
|
||||
unit=unit))
|
||||
auth = relation_get('auth', rid=rid, unit=unit)
|
||||
key = relation_get('key', rid=rid, unit=unit)
|
||||
|
||||
ctxt = {
|
||||
'mon_hosts': ' '.join(mon_hosts),
|
||||
'auth': auth,
|
||||
'key': key,
|
||||
}
|
||||
|
||||
if not os.path.isdir('/etc/ceph'):
|
||||
os.mkdir('/etc/ceph')
|
||||
|
||||
if not context_complete(ctxt):
|
||||
return {}
|
||||
|
||||
ensure_packages(['ceph-common'])
|
||||
|
||||
return ctxt
|
||||
|
||||
|
||||
class HAProxyContext(OSContextGenerator):
|
||||
interfaces = ['cluster']
|
||||
|
||||
def __call__(self):
|
||||
'''
|
||||
Builds half a context for the haproxy template, which describes
|
||||
all peers to be included in the cluster. Each charm needs to include
|
||||
its own context generator that describes the port mapping.
|
||||
'''
|
||||
if not relation_ids('cluster'):
|
||||
return {}
|
||||
|
||||
cluster_hosts = {}
|
||||
l_unit = local_unit().replace('/', '-')
|
||||
cluster_hosts[l_unit] = unit_get('private-address')
|
||||
|
||||
for rid in relation_ids('cluster'):
|
||||
for unit in related_units(rid):
|
||||
_unit = unit.replace('/', '-')
|
||||
addr = relation_get('private-address', rid=rid, unit=unit)
|
||||
cluster_hosts[_unit] = addr
|
||||
|
||||
ctxt = {
|
||||
'units': cluster_hosts,
|
||||
}
|
||||
if len(cluster_hosts.keys()) > 1:
|
||||
# Enable haproxy when we have enough peers.
|
||||
log('Ensuring haproxy enabled in /etc/default/haproxy.')
|
||||
with open('/etc/default/haproxy', 'w') as out:
|
||||
out.write('ENABLED=1\n')
|
||||
return ctxt
|
||||
log('HAProxy context is incomplete, this unit has no peers.')
|
||||
return {}
|
||||
|
||||
|
||||
class ImageServiceContext(OSContextGenerator):
|
||||
interfaces = ['image-service']
|
||||
|
||||
def __call__(self):
|
||||
'''
|
||||
Obtains the glance API server from the image-service relation. Useful
|
||||
in nova and cinder (currently).
|
||||
'''
|
||||
log('Generating template context for image-service.')
|
||||
rids = relation_ids('image-service')
|
||||
if not rids:
|
||||
return {}
|
||||
for rid in rids:
|
||||
for unit in related_units(rid):
|
||||
api_server = relation_get('glance-api-server',
|
||||
rid=rid, unit=unit)
|
||||
if api_server:
|
||||
return {'glance_api_servers': api_server}
|
||||
log('ImageService context is incomplete. '
|
||||
'Missing required relation data.')
|
||||
return {}
|
||||
|
||||
|
||||
class ApacheSSLContext(OSContextGenerator):
|
||||
"""
|
||||
Generates a context for an apache vhost configuration that configures
|
||||
HTTPS reverse proxying for one or many endpoints. Generated context
|
||||
looks something like:
|
||||
{
|
||||
'namespace': 'cinder',
|
||||
'private_address': 'iscsi.mycinderhost.com',
|
||||
'endpoints': [(8776, 8766), (8777, 8767)]
|
||||
}
|
||||
|
||||
The endpoints list consists of a tuples mapping external ports
|
||||
to internal ports.
|
||||
"""
|
||||
interfaces = ['https']
|
||||
|
||||
# charms should inherit this context and set external ports
|
||||
# and service namespace accordingly.
|
||||
external_ports = []
|
||||
service_namespace = None
|
||||
|
||||
def enable_modules(self):
|
||||
cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http']
|
||||
check_call(cmd)
|
||||
|
||||
def configure_cert(self):
|
||||
if not os.path.isdir('/etc/apache2/ssl'):
|
||||
os.mkdir('/etc/apache2/ssl')
|
||||
ssl_dir = os.path.join('/etc/apache2/ssl/', self.service_namespace)
|
||||
if not os.path.isdir(ssl_dir):
|
||||
os.mkdir(ssl_dir)
|
||||
cert, key = get_cert()
|
||||
with open(os.path.join(ssl_dir, 'cert'), 'w') as cert_out:
|
||||
cert_out.write(b64decode(cert))
|
||||
with open(os.path.join(ssl_dir, 'key'), 'w') as key_out:
|
||||
key_out.write(b64decode(key))
|
||||
ca_cert = get_ca_cert()
|
||||
if ca_cert:
|
||||
with open(CA_CERT_PATH, 'w') as ca_out:
|
||||
ca_out.write(b64decode(ca_cert))
|
||||
check_call(['update-ca-certificates'])
|
||||
|
||||
def __call__(self):
|
||||
if isinstance(self.external_ports, basestring):
|
||||
self.external_ports = [self.external_ports]
|
||||
if (not self.external_ports or not https()):
|
||||
return {}
|
||||
|
||||
self.configure_cert()
|
||||
self.enable_modules()
|
||||
|
||||
ctxt = {
|
||||
'namespace': self.service_namespace,
|
||||
'private_address': unit_get('private-address'),
|
||||
'endpoints': []
|
||||
}
|
||||
for ext_port in self.external_ports:
|
||||
if peer_units() or is_clustered():
|
||||
int_port = determine_haproxy_port(ext_port)
|
||||
else:
|
||||
int_port = determine_api_port(ext_port)
|
||||
portmap = (int(ext_port), int(int_port))
|
||||
ctxt['endpoints'].append(portmap)
|
||||
return ctxt
|
||||
|
||||
|
||||
class NeutronContext(object):
|
||||
interfaces = []
|
||||
|
||||
@property
|
||||
def plugin(self):
|
||||
return None
|
||||
|
||||
@property
|
||||
def network_manager(self):
|
||||
return None
|
||||
|
||||
@property
|
||||
def packages(self):
|
||||
return neutron_plugin_attribute(
|
||||
self.plugin, 'packages', self.network_manager)
|
||||
|
||||
@property
|
||||
def neutron_security_groups(self):
|
||||
return None
|
||||
|
||||
def _ensure_packages(self):
|
||||
[ensure_packages(pkgs) for pkgs in self.packages]
|
||||
|
||||
def _save_flag_file(self):
|
||||
if self.network_manager == 'quantum':
|
||||
_file = '/etc/nova/quantum_plugin.conf'
|
||||
else:
|
||||
_file = '/etc/nova/neutron_plugin.conf'
|
||||
with open(_file, 'wb') as out:
|
||||
out.write(self.plugin + '\n')
|
||||
|
||||
def ovs_ctxt(self):
|
||||
driver = neutron_plugin_attribute(self.plugin, 'driver',
|
||||
self.network_manager)
|
||||
|
||||
ovs_ctxt = {
|
||||
'core_plugin': driver,
|
||||
'neutron_plugin': 'ovs',
|
||||
'neutron_security_groups': self.neutron_security_groups,
|
||||
'local_ip': unit_private_ip(),
|
||||
}
|
||||
|
||||
return ovs_ctxt
|
||||
|
||||
def __call__(self):
|
||||
self._ensure_packages()
|
||||
|
||||
if self.network_manager not in ['quantum', 'neutron']:
|
||||
return {}
|
||||
|
||||
if not self.plugin:
|
||||
return {}
|
||||
|
||||
ctxt = {'network_manager': self.network_manager}
|
||||
|
||||
if self.plugin == 'ovs':
|
||||
ctxt.update(self.ovs_ctxt())
|
||||
|
||||
self._save_flag_file()
|
||||
return ctxt
|
||||
|
||||
|
||||
class OSConfigFlagContext(OSContextGenerator):
|
||||
'''
|
||||
Responsible adding user-defined config-flags in charm config to a
|
||||
to a template context.
|
||||
'''
|
||||
def __call__(self):
|
||||
config_flags = config('config-flags')
|
||||
if not config_flags or config_flags in ['None', '']:
|
||||
return {}
|
||||
config_flags = config_flags.split(',')
|
||||
flags = {}
|
||||
for flag in config_flags:
|
||||
if '=' not in flag:
|
||||
log('Improperly formatted config-flag, expected k=v '
|
||||
'got %s' % flag, level=WARNING)
|
||||
continue
|
||||
k, v = flag.split('=')
|
||||
flags[k.strip()] = v
|
||||
ctxt = {'user_config_flags': flags}
|
||||
return ctxt
|
|
@ -0,0 +1,117 @@
|
|||
# Various utilies for dealing with Neutron and the renaming from Quantum.
|
||||
|
||||
from subprocess import check_output
|
||||
|
||||
from charmhelpers.core.hookenv import (
|
||||
config,
|
||||
log,
|
||||
ERROR,
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.openstack.utils import os_release
|
||||
|
||||
|
||||
def headers_package():
|
||||
"""Ensures correct linux-headers for running kernel are installed,
|
||||
for building DKMS package"""
|
||||
kver = check_output(['uname', '-r']).strip()
|
||||
return 'linux-headers-%s' % kver
|
||||
|
||||
|
||||
# legacy
|
||||
def quantum_plugins():
|
||||
from charmhelpers.contrib.openstack import context
|
||||
return {
|
||||
'ovs': {
|
||||
'config': '/etc/quantum/plugins/openvswitch/'
|
||||
'ovs_quantum_plugin.ini',
|
||||
'driver': 'quantum.plugins.openvswitch.ovs_quantum_plugin.'
|
||||
'OVSQuantumPluginV2',
|
||||
'contexts': [
|
||||
context.SharedDBContext(user=config('neutron-database-user'),
|
||||
database=config('neutron-database'),
|
||||
relation_prefix='neutron')],
|
||||
'services': ['quantum-plugin-openvswitch-agent'],
|
||||
'packages': [[headers_package(), 'openvswitch-datapath-dkms'],
|
||||
['quantum-plugin-openvswitch-agent']],
|
||||
},
|
||||
'nvp': {
|
||||
'config': '/etc/quantum/plugins/nicira/nvp.ini',
|
||||
'driver': 'quantum.plugins.nicira.nicira_nvp_plugin.'
|
||||
'QuantumPlugin.NvpPluginV2',
|
||||
'services': [],
|
||||
'packages': [],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def neutron_plugins():
|
||||
from charmhelpers.contrib.openstack import context
|
||||
return {
|
||||
'ovs': {
|
||||
'config': '/etc/neutron/plugins/openvswitch/'
|
||||
'ovs_neutron_plugin.ini',
|
||||
'driver': 'neutron.plugins.openvswitch.ovs_neutron_plugin.'
|
||||
'OVSNeutronPluginV2',
|
||||
'contexts': [
|
||||
context.SharedDBContext(user=config('neutron-database-user'),
|
||||
database=config('neutron-database'),
|
||||
relation_prefix='neutron')],
|
||||
'services': ['neutron-plugin-openvswitch-agent'],
|
||||
'packages': [[headers_package(), 'openvswitch-datapath-dkms'],
|
||||
['quantum-plugin-openvswitch-agent']],
|
||||
},
|
||||
'nvp': {
|
||||
'config': '/etc/neutron/plugins/nicira/nvp.ini',
|
||||
'driver': 'neutron.plugins.nicira.nicira_nvp_plugin.'
|
||||
'NeutronPlugin.NvpPluginV2',
|
||||
'services': [],
|
||||
'packages': [],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def neutron_plugin_attribute(plugin, attr, net_manager=None):
|
||||
manager = net_manager or network_manager()
|
||||
if manager == 'quantum':
|
||||
plugins = quantum_plugins()
|
||||
elif manager == 'neutron':
|
||||
plugins = neutron_plugins()
|
||||
else:
|
||||
log('Error: Network manager does not support plugins.')
|
||||
raise Exception
|
||||
|
||||
try:
|
||||
_plugin = plugins[plugin]
|
||||
except KeyError:
|
||||
log('Unrecognised plugin for %s: %s' % (manager, plugin), level=ERROR)
|
||||
raise
|
||||
|
||||
try:
|
||||
return _plugin[attr]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
|
||||
def network_manager():
|
||||
'''
|
||||
Deals with the renaming of Quantum to Neutron in H and any situations
|
||||
that require compatability (eg, deploying H with network-manager=quantum,
|
||||
upgrading from G).
|
||||
'''
|
||||
release = os_release('nova-common')
|
||||
manager = config('network-manager').lower()
|
||||
|
||||
if manager not in ['quantum', 'neutron']:
|
||||
return manager
|
||||
|
||||
if release in ['essex']:
|
||||
# E does not support neutron
|
||||
log('Neutron networking not supported in Essex.', level=ERROR)
|
||||
raise
|
||||
elif release in ['folsom', 'grizzly']:
|
||||
# neutron is named quantum in F and G
|
||||
return 'quantum'
|
||||
else:
|
||||
# ensure accurate naming for all releases post-H
|
||||
return 'neutron'
|
|
@ -0,0 +1,2 @@
|
|||
# dummy __init__.py to fool syncer into thinking this is a syncable python
|
||||
# module
|
|
@ -0,0 +1,280 @@
|
|||
import os
|
||||
|
||||
from charmhelpers.fetch import apt_install
|
||||
|
||||
from charmhelpers.core.hookenv import (
|
||||
log,
|
||||
ERROR,
|
||||
INFO
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.openstack.utils import OPENSTACK_CODENAMES
|
||||
|
||||
try:
|
||||
from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions
|
||||
except ImportError:
|
||||
# python-jinja2 may not be installed yet, or we're running unittests.
|
||||
FileSystemLoader = ChoiceLoader = Environment = exceptions = None
|
||||
|
||||
|
||||
class OSConfigException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def get_loader(templates_dir, os_release):
|
||||
"""
|
||||
Create a jinja2.ChoiceLoader containing template dirs up to
|
||||
and including os_release. If directory template directory
|
||||
is missing at templates_dir, it will be omitted from the loader.
|
||||
templates_dir is added to the bottom of the search list as a base
|
||||
loading dir.
|
||||
|
||||
A charm may also ship a templates dir with this module
|
||||
and it will be appended to the bottom of the search list, eg:
|
||||
hooks/charmhelpers/contrib/openstack/templates.
|
||||
|
||||
:param templates_dir: str: Base template directory containing release
|
||||
sub-directories.
|
||||
:param os_release : str: OpenStack release codename to construct template
|
||||
loader.
|
||||
|
||||
:returns : jinja2.ChoiceLoader constructed with a list of
|
||||
jinja2.FilesystemLoaders, ordered in descending
|
||||
order by OpenStack release.
|
||||
"""
|
||||
tmpl_dirs = [(rel, os.path.join(templates_dir, rel))
|
||||
for rel in OPENSTACK_CODENAMES.itervalues()]
|
||||
|
||||
if not os.path.isdir(templates_dir):
|
||||
log('Templates directory not found @ %s.' % templates_dir,
|
||||
level=ERROR)
|
||||
raise OSConfigException
|
||||
|
||||
# the bottom contains tempaltes_dir and possibly a common templates dir
|
||||
# shipped with the helper.
|
||||
loaders = [FileSystemLoader(templates_dir)]
|
||||
helper_templates = os.path.join(os.path.dirname(__file__), 'templates')
|
||||
if os.path.isdir(helper_templates):
|
||||
loaders.append(FileSystemLoader(helper_templates))
|
||||
|
||||
for rel, tmpl_dir in tmpl_dirs:
|
||||
if os.path.isdir(tmpl_dir):
|
||||
loaders.insert(0, FileSystemLoader(tmpl_dir))
|
||||
if rel == os_release:
|
||||
break
|
||||
log('Creating choice loader with dirs: %s' %
|
||||
[l.searchpath for l in loaders], level=INFO)
|
||||
return ChoiceLoader(loaders)
|
||||
|
||||
|
||||
class OSConfigTemplate(object):
|
||||
"""
|
||||
Associates a config file template with a list of context generators.
|
||||
Responsible for constructing a template context based on those generators.
|
||||
"""
|
||||
def __init__(self, config_file, contexts):
|
||||
self.config_file = config_file
|
||||
|
||||
if hasattr(contexts, '__call__'):
|
||||
self.contexts = [contexts]
|
||||
else:
|
||||
self.contexts = contexts
|
||||
|
||||
self._complete_contexts = []
|
||||
|
||||
def context(self):
|
||||
ctxt = {}
|
||||
for context in self.contexts:
|
||||
_ctxt = context()
|
||||
if _ctxt:
|
||||
ctxt.update(_ctxt)
|
||||
# track interfaces for every complete context.
|
||||
[self._complete_contexts.append(interface)
|
||||
for interface in context.interfaces
|
||||
if interface not in self._complete_contexts]
|
||||
return ctxt
|
||||
|
||||
def complete_contexts(self):
|
||||
'''
|
||||
Return a list of interfaces that have atisfied contexts.
|
||||
'''
|
||||
if self._complete_contexts:
|
||||
return self._complete_contexts
|
||||
self.context()
|
||||
return self._complete_contexts
|
||||
|
||||
|
||||
class OSConfigRenderer(object):
|
||||
"""
|
||||
This class provides a common templating system to be used by OpenStack
|
||||
charms. It is intended to help charms share common code and templates,
|
||||
and ease the burden of managing config templates across multiple OpenStack
|
||||
releases.
|
||||
|
||||
Basic usage:
|
||||
# import some common context generates from charmhelpers
|
||||
from charmhelpers.contrib.openstack import context
|
||||
|
||||
# Create a renderer object for a specific OS release.
|
||||
configs = OSConfigRenderer(templates_dir='/tmp/templates',
|
||||
openstack_release='folsom')
|
||||
# register some config files with context generators.
|
||||
configs.register(config_file='/etc/nova/nova.conf',
|
||||
contexts=[context.SharedDBContext(),
|
||||
context.AMQPContext()])
|
||||
configs.register(config_file='/etc/nova/api-paste.ini',
|
||||
contexts=[context.IdentityServiceContext()])
|
||||
configs.register(config_file='/etc/haproxy/haproxy.conf',
|
||||
contexts=[context.HAProxyContext()])
|
||||
# write out a single config
|
||||
configs.write('/etc/nova/nova.conf')
|
||||
# write out all registered configs
|
||||
configs.write_all()
|
||||
|
||||
Details:
|
||||
|
||||
OpenStack Releases and template loading
|
||||
---------------------------------------
|
||||
When the object is instantiated, it is associated with a specific OS
|
||||
release. This dictates how the template loader will be constructed.
|
||||
|
||||
The constructed loader attempts to load the template from several places
|
||||
in the following order:
|
||||
- from the most recent OS release-specific template dir (if one exists)
|
||||
- the base templates_dir
|
||||
- a template directory shipped in the charm with this helper file.
|
||||
|
||||
|
||||
For the example above, '/tmp/templates' contains the following structure:
|
||||
/tmp/templates/nova.conf
|
||||
/tmp/templates/api-paste.ini
|
||||
/tmp/templates/grizzly/api-paste.ini
|
||||
/tmp/templates/havana/api-paste.ini
|
||||
|
||||
Since it was registered with the grizzly release, it first seraches
|
||||
the grizzly directory for nova.conf, then the templates dir.
|
||||
|
||||
When writing api-paste.ini, it will find the template in the grizzly
|
||||
directory.
|
||||
|
||||
If the object were created with folsom, it would fall back to the
|
||||
base templates dir for its api-paste.ini template.
|
||||
|
||||
This system should help manage changes in config files through
|
||||
openstack releases, allowing charms to fall back to the most recently
|
||||
updated config template for a given release
|
||||
|
||||
The haproxy.conf, since it is not shipped in the templates dir, will
|
||||
be loaded from the module directory's template directory, eg
|
||||
$CHARM/hooks/charmhelpers/contrib/openstack/templates. This allows
|
||||
us to ship common templates (haproxy, apache) with the helpers.
|
||||
|
||||
Context generators
|
||||
---------------------------------------
|
||||
Context generators are used to generate template contexts during hook
|
||||
execution. Doing so may require inspecting service relations, charm
|
||||
config, etc. When registered, a config file is associated with a list
|
||||
of generators. When a template is rendered and written, all context
|
||||
generates are called in a chain to generate the context dictionary
|
||||
passed to the jinja2 template. See context.py for more info.
|
||||
"""
|
||||
def __init__(self, templates_dir, openstack_release):
|
||||
if not os.path.isdir(templates_dir):
|
||||
log('Could not locate templates dir %s' % templates_dir,
|
||||
level=ERROR)
|
||||
raise OSConfigException
|
||||
|
||||
self.templates_dir = templates_dir
|
||||
self.openstack_release = openstack_release
|
||||
self.templates = {}
|
||||
self._tmpl_env = None
|
||||
|
||||
if None in [Environment, ChoiceLoader, FileSystemLoader]:
|
||||
# if this code is running, the object is created pre-install hook.
|
||||
# jinja2 shouldn't get touched until the module is reloaded on next
|
||||
# hook execution, with proper jinja2 bits successfully imported.
|
||||
apt_install('python-jinja2')
|
||||
|
||||
def register(self, config_file, contexts):
|
||||
"""
|
||||
Register a config file with a list of context generators to be called
|
||||
during rendering.
|
||||
"""
|
||||
self.templates[config_file] = OSConfigTemplate(config_file=config_file,
|
||||
contexts=contexts)
|
||||
log('Registered config file: %s' % config_file, level=INFO)
|
||||
|
||||
def _get_tmpl_env(self):
|
||||
if not self._tmpl_env:
|
||||
loader = get_loader(self.templates_dir, self.openstack_release)
|
||||
self._tmpl_env = Environment(loader=loader)
|
||||
|
||||
def _get_template(self, template):
|
||||
self._get_tmpl_env()
|
||||
template = self._tmpl_env.get_template(template)
|
||||
log('Loaded template from %s' % template.filename, level=INFO)
|
||||
return template
|
||||
|
||||
def render(self, config_file):
|
||||
if config_file not in self.templates:
|
||||
log('Config not registered: %s' % config_file, level=ERROR)
|
||||
raise OSConfigException
|
||||
ctxt = self.templates[config_file].context()
|
||||
|
||||
_tmpl = os.path.basename(config_file)
|
||||
try:
|
||||
template = self._get_template(_tmpl)
|
||||
except exceptions.TemplateNotFound:
|
||||
# if no template is found with basename, try looking for it
|
||||
# using a munged full path, eg:
|
||||
# /etc/apache2/apache2.conf -> etc_apache2_apache2.conf
|
||||
_tmpl = '_'.join(config_file.split('/')[1:])
|
||||
try:
|
||||
template = self._get_template(_tmpl)
|
||||
except exceptions.TemplateNotFound as e:
|
||||
log('Could not load template from %s by %s or %s.' %
|
||||
(self.templates_dir, os.path.basename(config_file), _tmpl),
|
||||
level=ERROR)
|
||||
raise e
|
||||
|
||||
log('Rendering from template: %s' % _tmpl, level=INFO)
|
||||
return template.render(ctxt)
|
||||
|
||||
def write(self, config_file):
|
||||
"""
|
||||
Write a single config file, raises if config file is not registered.
|
||||
"""
|
||||
if config_file not in self.templates:
|
||||
log('Config not registered: %s' % config_file, level=ERROR)
|
||||
raise OSConfigException
|
||||
|
||||
_out = self.render(config_file)
|
||||
|
||||
with open(config_file, 'wb') as out:
|
||||
out.write(_out)
|
||||
|
||||
log('Wrote template %s.' % config_file, level=INFO)
|
||||
|
||||
def write_all(self):
|
||||
"""
|
||||
Write out all registered config files.
|
||||
"""
|
||||
[self.write(k) for k in self.templates.iterkeys()]
|
||||
|
||||
def set_release(self, openstack_release):
|
||||
"""
|
||||
Resets the template environment and generates a new template loader
|
||||
based on a the new openstack release.
|
||||
"""
|
||||
self._tmpl_env = None
|
||||
self.openstack_release = openstack_release
|
||||
self._get_tmpl_env()
|
||||
|
||||
def complete_contexts(self):
|
||||
'''
|
||||
Returns a list of context interfaces that yield a complete context.
|
||||
'''
|
||||
interfaces = []
|
||||
[interfaces.extend(i.complete_contexts())
|
||||
for i in self.templates.itervalues()]
|
||||
return interfaces
|
|
@ -0,0 +1,365 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# Common python helper functions used for OpenStack charms.
|
||||
from collections import OrderedDict
|
||||
|
||||
import apt_pkg as apt
|
||||
import subprocess
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
|
||||
from charmhelpers.core.hookenv import (
|
||||
config,
|
||||
log as juju_log,
|
||||
charm_dir,
|
||||
)
|
||||
|
||||
from charmhelpers.core.host import (
|
||||
lsb_release,
|
||||
)
|
||||
|
||||
from charmhelpers.fetch import (
|
||||
apt_install,
|
||||
)
|
||||
|
||||
CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu"
|
||||
CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA'
|
||||
|
||||
UBUNTU_OPENSTACK_RELEASE = OrderedDict([
|
||||
('oneiric', 'diablo'),
|
||||
('precise', 'essex'),
|
||||
('quantal', 'folsom'),
|
||||
('raring', 'grizzly'),
|
||||
('saucy', 'havana'),
|
||||
])
|
||||
|
||||
|
||||
OPENSTACK_CODENAMES = OrderedDict([
|
||||
('2011.2', 'diablo'),
|
||||
('2012.1', 'essex'),
|
||||
('2012.2', 'folsom'),
|
||||
('2013.1', 'grizzly'),
|
||||
('2013.2', 'havana'),
|
||||
('2014.1', 'icehouse'),
|
||||
])
|
||||
|
||||
# The ugly duckling
|
||||
SWIFT_CODENAMES = OrderedDict([
|
||||
('1.4.3', 'diablo'),
|
||||
('1.4.8', 'essex'),
|
||||
('1.7.4', 'folsom'),
|
||||
('1.8.0', 'grizzly'),
|
||||
('1.7.7', 'grizzly'),
|
||||
('1.7.6', 'grizzly'),
|
||||
('1.10.0', 'havana'),
|
||||
('1.9.1', 'havana'),
|
||||
('1.9.0', 'havana'),
|
||||
])
|
||||
|
||||
|
||||
def error_out(msg):
|
||||
juju_log("FATAL ERROR: %s" % msg, level='ERROR')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_os_codename_install_source(src):
|
||||
'''Derive OpenStack release codename from a given installation source.'''
|
||||
ubuntu_rel = lsb_release()['DISTRIB_CODENAME']
|
||||
rel = ''
|
||||
if src == 'distro':
|
||||
try:
|
||||
rel = UBUNTU_OPENSTACK_RELEASE[ubuntu_rel]
|
||||
except KeyError:
|
||||
e = 'Could not derive openstack release for '\
|
||||
'this Ubuntu release: %s' % ubuntu_rel
|
||||
error_out(e)
|
||||
return rel
|
||||
|
||||
if src.startswith('cloud:'):
|
||||
ca_rel = src.split(':')[1]
|
||||
ca_rel = ca_rel.split('%s-' % ubuntu_rel)[1].split('/')[0]
|
||||
return ca_rel
|
||||
|
||||
# Best guess match based on deb string provided
|
||||
if src.startswith('deb') or src.startswith('ppa'):
|
||||
for k, v in OPENSTACK_CODENAMES.iteritems():
|
||||
if v in src:
|
||||
return v
|
||||
|
||||
|
||||
def get_os_version_install_source(src):
|
||||
codename = get_os_codename_install_source(src)
|
||||
return get_os_version_codename(codename)
|
||||
|
||||
|
||||
def get_os_codename_version(vers):
|
||||
'''Determine OpenStack codename from version number.'''
|
||||
try:
|
||||
return OPENSTACK_CODENAMES[vers]
|
||||
except KeyError:
|
||||
e = 'Could not determine OpenStack codename for version %s' % vers
|
||||
error_out(e)
|
||||
|
||||
|
||||
def get_os_version_codename(codename):
|
||||
'''Determine OpenStack version number from codename.'''
|
||||
for k, v in OPENSTACK_CODENAMES.iteritems():
|
||||
if v == codename:
|
||||
return k
|
||||
e = 'Could not derive OpenStack version for '\
|
||||
'codename: %s' % codename
|
||||
error_out(e)
|
||||
|
||||
|
||||
def get_os_codename_package(package, fatal=True):
|
||||
'''Derive OpenStack release codename from an installed package.'''
|
||||
apt.init()
|
||||
cache = apt.Cache()
|
||||
|
||||
try:
|
||||
pkg = cache[package]
|
||||
except:
|
||||
if not fatal:
|
||||
return None
|
||||
# the package is unknown to the current apt cache.
|
||||
e = 'Could not determine version of package with no installation '\
|
||||
'candidate: %s' % package
|
||||
error_out(e)
|
||||
|
||||
if not pkg.current_ver:
|
||||
if not fatal:
|
||||
return None
|
||||
# package is known, but no version is currently installed.
|
||||
e = 'Could not determine version of uninstalled package: %s' % package
|
||||
error_out(e)
|
||||
|
||||
vers = apt.upstream_version(pkg.current_ver.ver_str)
|
||||
|
||||
try:
|
||||
if 'swift' in pkg.name:
|
||||
swift_vers = vers[:5]
|
||||
if swift_vers not in SWIFT_CODENAMES:
|
||||
# Deal with 1.10.0 upward
|
||||
swift_vers = vers[:6]
|
||||
return SWIFT_CODENAMES[swift_vers]
|
||||
else:
|
||||
vers = vers[:6]
|
||||
return OPENSTACK_CODENAMES[vers]
|
||||
except KeyError:
|
||||
e = 'Could not determine OpenStack codename for version %s' % vers
|
||||
error_out(e)
|
||||
|
||||
|
||||
def get_os_version_package(pkg, fatal=True):
|
||||
'''Derive OpenStack version number from an installed package.'''
|
||||
codename = get_os_codename_package(pkg, fatal=fatal)
|
||||
|
||||
if not codename:
|
||||
return None
|
||||
|
||||
if 'swift' in pkg:
|
||||
vers_map = SWIFT_CODENAMES
|
||||
else:
|
||||
vers_map = OPENSTACK_CODENAMES
|
||||
|
||||
for version, cname in vers_map.iteritems():
|
||||
if cname == codename:
|
||||
return version
|
||||
#e = "Could not determine OpenStack version for package: %s" % pkg
|
||||
#error_out(e)
|
||||
|
||||
|
||||
os_rel = None
|
||||
|
||||
|
||||
def os_release(package, base='essex'):
|
||||
'''
|
||||
Returns OpenStack release codename from a cached global.
|
||||
If the codename can not be determined from either an installed package or
|
||||
the installation source, the earliest release supported by the charm should
|
||||
be returned.
|
||||
'''
|
||||
global os_rel
|
||||
if os_rel:
|
||||
return os_rel
|
||||
os_rel = (get_os_codename_package(package, fatal=False) or
|
||||
get_os_codename_install_source(config('openstack-origin')) or
|
||||
base)
|
||||
return os_rel
|
||||
|
||||
|
||||
def import_key(keyid):
|
||||
cmd = "apt-key adv --keyserver keyserver.ubuntu.com " \
|
||||
"--recv-keys %s" % keyid
|
||||
try:
|
||||
subprocess.check_call(cmd.split(' '))
|
||||
except subprocess.CalledProcessError:
|
||||
error_out("Error importing repo key %s" % keyid)
|
||||
|
||||
|
||||
def configure_installation_source(rel):
|
||||
'''Configure apt installation source.'''
|
||||
if rel == 'distro':
|
||||
return
|
||||
elif rel[:4] == "ppa:":
|
||||
src = rel
|
||||
subprocess.check_call(["add-apt-repository", "-y", src])
|
||||
elif rel[:3] == "deb":
|
||||
l = len(rel.split('|'))
|
||||
if l == 2:
|
||||
src, key = rel.split('|')
|
||||
juju_log("Importing PPA key from keyserver for %s" % src)
|
||||
import_key(key)
|
||||
elif l == 1:
|
||||
src = rel
|
||||
with open('/etc/apt/sources.list.d/juju_deb.list', 'w') as f:
|
||||
f.write(src)
|
||||
elif rel[:6] == 'cloud:':
|
||||
ubuntu_rel = lsb_release()['DISTRIB_CODENAME']
|
||||
rel = rel.split(':')[1]
|
||||
u_rel = rel.split('-')[0]
|
||||
ca_rel = rel.split('-')[1]
|
||||
|
||||
if u_rel != ubuntu_rel:
|
||||
e = 'Cannot install from Cloud Archive pocket %s on this Ubuntu '\
|
||||
'version (%s)' % (ca_rel, ubuntu_rel)
|
||||
error_out(e)
|
||||
|
||||
if 'staging' in ca_rel:
|
||||
# staging is just a regular PPA.
|
||||
os_rel = ca_rel.split('/')[0]
|
||||
ppa = 'ppa:ubuntu-cloud-archive/%s-staging' % os_rel
|
||||
cmd = 'add-apt-repository -y %s' % ppa
|
||||
subprocess.check_call(cmd.split(' '))
|
||||
return
|
||||
|
||||
# map charm config options to actual archive pockets.
|
||||
pockets = {
|
||||
'folsom': 'precise-updates/folsom',
|
||||
'folsom/updates': 'precise-updates/folsom',
|
||||
'folsom/proposed': 'precise-proposed/folsom',
|
||||
'grizzly': 'precise-updates/grizzly',
|
||||
'grizzly/updates': 'precise-updates/grizzly',
|
||||
'grizzly/proposed': 'precise-proposed/grizzly',
|
||||
'havana': 'precise-updates/havana',
|
||||
'havana/updates': 'precise-updates/havana',
|
||||
'havana/proposed': 'precise-proposed/havana',
|
||||
}
|
||||
|
||||
try:
|
||||
pocket = pockets[ca_rel]
|
||||
except KeyError:
|
||||
e = 'Invalid Cloud Archive release specified: %s' % rel
|
||||
error_out(e)
|
||||
|
||||
src = "deb %s %s main" % (CLOUD_ARCHIVE_URL, pocket)
|
||||
apt_install('ubuntu-cloud-keyring', fatal=True)
|
||||
|
||||
with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as f:
|
||||
f.write(src)
|
||||
else:
|
||||
error_out("Invalid openstack-release specified: %s" % rel)
|
||||
|
||||
|
||||
def save_script_rc(script_path="scripts/scriptrc", **env_vars):
|
||||
"""
|
||||
Write an rc file in the charm-delivered directory containing
|
||||
exported environment variables provided by env_vars. Any charm scripts run
|
||||
outside the juju hook environment can source this scriptrc to obtain
|
||||
updated config information necessary to perform health checks or
|
||||
service changes.
|
||||
"""
|
||||
juju_rc_path = "%s/%s" % (charm_dir(), script_path)
|
||||
if not os.path.exists(os.path.dirname(juju_rc_path)):
|
||||
os.mkdir(os.path.dirname(juju_rc_path))
|
||||
with open(juju_rc_path, 'wb') as rc_script:
|
||||
rc_script.write(
|
||||
"#!/bin/bash\n")
|
||||
[rc_script.write('export %s=%s\n' % (u, p))
|
||||
for u, p in env_vars.iteritems() if u != "script_path"]
|
||||
|
||||
|
||||
def openstack_upgrade_available(package):
|
||||
"""
|
||||
Determines if an OpenStack upgrade is available from installation
|
||||
source, based on version of installed package.
|
||||
|
||||
:param package: str: Name of installed package.
|
||||
|
||||
:returns: bool: : Returns True if configured installation source offers
|
||||
a newer version of package.
|
||||
|
||||
"""
|
||||
|
||||
src = config('openstack-origin')
|
||||
cur_vers = get_os_version_package(package)
|
||||
available_vers = get_os_version_install_source(src)
|
||||
apt.init()
|
||||
return apt.version_compare(available_vers, cur_vers) == 1
|
||||
|
||||
|
||||
def is_ip(address):
|
||||
"""
|
||||
Returns True if address is a valid IP address.
|
||||
"""
|
||||
try:
|
||||
# Test to see if already an IPv4 address
|
||||
socket.inet_aton(address)
|
||||
return True
|
||||
except socket.error:
|
||||
return False
|
||||
|
||||
|
||||
def ns_query(address):
|
||||
try:
|
||||
import dns.resolver
|
||||
except ImportError:
|
||||
apt_install('python-dnspython')
|
||||
import dns.resolver
|
||||
|
||||
if isinstance(address, dns.name.Name):
|
||||
rtype = 'PTR'
|
||||
elif isinstance(address, basestring):
|
||||
rtype = 'A'
|
||||
|
||||
answers = dns.resolver.query(address, rtype)
|
||||
if answers:
|
||||
return str(answers[0])
|
||||
return None
|
||||
|
||||
|
||||
def get_host_ip(hostname):
|
||||
"""
|
||||
Resolves the IP for a given hostname, or returns
|
||||
the input if it is already an IP.
|
||||
"""
|
||||
if is_ip(hostname):
|
||||
return hostname
|
||||
|
||||
return ns_query(hostname)
|
||||
|
||||
|
||||
def get_hostname(address):
|
||||
"""
|
||||
Resolves hostname for given IP, or returns the input
|
||||
if it is already a hostname.
|
||||
"""
|
||||
if not is_ip(address):
|
||||
return address
|
||||
|
||||
try:
|
||||
import dns.reversename
|
||||
except ImportError:
|
||||
apt_install('python-dnspython')
|
||||
import dns.reversename
|
||||
|
||||
rev = dns.reversename.from_address(address)
|
||||
result = ns_query(rev)
|
||||
if not result:
|
||||
return None
|
||||
|
||||
# strip trailing .
|
||||
if result.endswith('.'):
|
||||
return result[:-1]
|
||||
return result
|
|
@ -0,0 +1,340 @@
|
|||
"Interactions with the Juju environment"
|
||||
# Copyright 2013 Canonical Ltd.
|
||||
#
|
||||
# Authors:
|
||||
# Charm Helpers Developers <juju@lists.ubuntu.com>
|
||||
|
||||
import os
|
||||
import json
|
||||
import yaml
|
||||
import subprocess
|
||||
import UserDict
|
||||
|
||||
CRITICAL = "CRITICAL"
|
||||
ERROR = "ERROR"
|
||||
WARNING = "WARNING"
|
||||
INFO = "INFO"
|
||||
DEBUG = "DEBUG"
|
||||
MARKER = object()
|
||||
|
||||
cache = {}
|
||||
|
||||
|
||||
def cached(func):
|
||||
''' Cache return values for multiple executions of func + args
|
||||
|
||||
For example:
|
||||
|
||||
@cached
|
||||
def unit_get(attribute):
|
||||
pass
|
||||
|
||||
unit_get('test')
|
||||
|
||||
will cache the result of unit_get + 'test' for future calls.
|
||||
'''
|
||||
def wrapper(*args, **kwargs):
|
||||
global cache
|
||||
key = str((func, args, kwargs))
|
||||
try:
|
||||
return cache[key]
|
||||
except KeyError:
|
||||
res = func(*args, **kwargs)
|
||||
cache[key] = res
|
||||
return res
|
||||
return wrapper
|
||||
|
||||
|
||||
def flush(key):
|
||||
''' Flushes any entries from function cache where the
|
||||
key is found in the function+args '''
|
||||
flush_list = []
|
||||
for item in cache:
|
||||
if key in item:
|
||||
flush_list.append(item)
|
||||
for item in flush_list:
|
||||
del cache[item]
|
||||
|
||||
|
||||
def log(message, level=None):
|
||||
"Write a message to the juju log"
|
||||
command = ['juju-log']
|
||||
if level:
|
||||
command += ['-l', level]
|
||||
command += [message]
|
||||
subprocess.call(command)
|
||||
|
||||
|
||||
class Serializable(UserDict.IterableUserDict):
|
||||
"Wrapper, an object that can be serialized to yaml or json"
|
||||
|
||||
def __init__(self, obj):
|
||||
# wrap the object
|
||||
UserDict.IterableUserDict.__init__(self)
|
||||
self.data = obj
|
||||
|
||||
def __getattr__(self, attr):
|
||||
# See if this object has attribute.
|
||||
if attr in ("json", "yaml", "data"):
|
||||
return self.__dict__[attr]
|
||||
# Check for attribute in wrapped object.
|
||||
got = getattr(self.data, attr, MARKER)
|
||||
if got is not MARKER:
|
||||
return got
|
||||
# Proxy to the wrapped object via dict interface.
|
||||
try:
|
||||
return self.data[attr]
|
||||
except KeyError:
|
||||
raise AttributeError(attr)
|
||||
|
||||
def __getstate__(self):
|
||||
# Pickle as a standard dictionary.
|
||||
return self.data
|
||||
|
||||
def __setstate__(self, state):
|
||||
# Unpickle into our wrapper.
|
||||
self.data = state
|
||||
|
||||
def json(self):
|
||||
"Serialize the object to json"
|
||||
return json.dumps(self.data)
|
||||
|
||||
def yaml(self):
|
||||
"Serialize the object to yaml"
|
||||
return yaml.dump(self.data)
|
||||
|
||||
|
||||
def execution_environment():
|
||||
"""A convenient bundling of the current execution context"""
|
||||
context = {}
|
||||
context['conf'] = config()
|
||||
if relation_id():
|
||||
context['reltype'] = relation_type()
|
||||
context['relid'] = relation_id()
|
||||
context['rel'] = relation_get()
|
||||
context['unit'] = local_unit()
|
||||
context['rels'] = relations()
|
||||
context['env'] = os.environ
|
||||
return context
|
||||
|
||||
|
||||
def in_relation_hook():
|
||||
"Determine whether we're running in a relation hook"
|
||||
return 'JUJU_RELATION' in os.environ
|
||||
|
||||
|
||||
def relation_type():
|
||||
"The scope for the current relation hook"
|
||||
return os.environ.get('JUJU_RELATION', None)
|
||||
|
||||
|
||||
def relation_id():
|
||||
"The relation ID for the current relation hook"
|
||||
return os.environ.get('JUJU_RELATION_ID', None)
|
||||
|
||||
|
||||
def local_unit():
|
||||
"Local unit ID"
|
||||
return os.environ['JUJU_UNIT_NAME']
|
||||
|
||||
|
||||
def remote_unit():
|
||||
"The remote unit for the current relation hook"
|
||||
return os.environ['JUJU_REMOTE_UNIT']
|
||||
|
||||
|
||||
def service_name():
|
||||
"The name service group this unit belongs to"
|
||||
return local_unit().split('/')[0]
|
||||
|
||||
|
||||
@cached
|
||||
def config(scope=None):
|
||||
"Juju charm configuration"
|
||||
config_cmd_line = ['config-get']
|
||||
if scope is not None:
|
||||
config_cmd_line.append(scope)
|
||||
config_cmd_line.append('--format=json')
|
||||
try:
|
||||
return json.loads(subprocess.check_output(config_cmd_line))
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
@cached
|
||||
def relation_get(attribute=None, unit=None, rid=None):
|
||||
_args = ['relation-get', '--format=json']
|
||||
if rid:
|
||||
_args.append('-r')
|
||||
_args.append(rid)
|
||||
_args.append(attribute or '-')
|
||||
if unit:
|
||||
_args.append(unit)
|
||||
try:
|
||||
return json.loads(subprocess.check_output(_args))
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def relation_set(relation_id=None, relation_settings={}, **kwargs):
|
||||
relation_cmd_line = ['relation-set']
|
||||
if relation_id is not None:
|
||||
relation_cmd_line.extend(('-r', relation_id))
|
||||
for k, v in (relation_settings.items() + kwargs.items()):
|
||||
if v is None:
|
||||
relation_cmd_line.append('{}='.format(k))
|
||||
else:
|
||||
relation_cmd_line.append('{}={}'.format(k, v))
|
||||
subprocess.check_call(relation_cmd_line)
|
||||
# Flush cache of any relation-gets for local unit
|
||||
flush(local_unit())
|
||||
|
||||
|
||||
@cached
|
||||
def relation_ids(reltype=None):
|
||||
"A list of relation_ids"
|
||||
reltype = reltype or relation_type()
|
||||
relid_cmd_line = ['relation-ids', '--format=json']
|
||||
if reltype is not None:
|
||||
relid_cmd_line.append(reltype)
|
||||
return json.loads(subprocess.check_output(relid_cmd_line)) or []
|
||||
return []
|
||||
|
||||
|
||||
@cached
|
||||
def related_units(relid=None):
|
||||
"A list of related units"
|
||||
relid = relid or relation_id()
|
||||
units_cmd_line = ['relation-list', '--format=json']
|
||||
if relid is not None:
|
||||
units_cmd_line.extend(('-r', relid))
|
||||
return json.loads(subprocess.check_output(units_cmd_line)) or []
|
||||
|
||||
|
||||
@cached
|
||||
def relation_for_unit(unit=None, rid=None):
|
||||
"Get the json represenation of a unit's relation"
|
||||
unit = unit or remote_unit()
|
||||
relation = relation_get(unit=unit, rid=rid)
|
||||
for key in relation:
|
||||
if key.endswith('-list'):
|
||||
relation[key] = relation[key].split()
|
||||
relation['__unit__'] = unit
|
||||
return relation
|
||||
|
||||
|
||||
@cached
|
||||
def relations_for_id(relid=None):
|
||||
"Get relations of a specific relation ID"
|
||||
relation_data = []
|
||||
relid = relid or relation_ids()
|
||||
for unit in related_units(relid):
|
||||
unit_data = relation_for_unit(unit, relid)
|
||||
unit_data['__relid__'] = relid
|
||||
relation_data.append(unit_data)
|
||||
return relation_data
|
||||
|
||||
|
||||
@cached
|
||||
def relations_of_type(reltype=None):
|
||||
"Get relations of a specific type"
|
||||
relation_data = []
|
||||
reltype = reltype or relation_type()
|
||||
for relid in relation_ids(reltype):
|
||||
for relation in relations_for_id(relid):
|
||||
relation['__relid__'] = relid
|
||||
relation_data.append(relation)
|
||||
return relation_data
|
||||
|
||||
|
||||
@cached
|
||||
def relation_types():
|
||||
"Get a list of relation types supported by this charm"
|
||||
charmdir = os.environ.get('CHARM_DIR', '')
|
||||
mdf = open(os.path.join(charmdir, 'metadata.yaml'))
|
||||
md = yaml.safe_load(mdf)
|
||||
rel_types = []
|
||||
for key in ('provides', 'requires', 'peers'):
|
||||
section = md.get(key)
|
||||
if section:
|
||||
rel_types.extend(section.keys())
|
||||
mdf.close()
|
||||
return rel_types
|
||||
|
||||
|
||||
@cached
|
||||
def relations():
|
||||
rels = {}
|
||||
for reltype in relation_types():
|
||||
relids = {}
|
||||
for relid in relation_ids(reltype):
|
||||
units = {local_unit(): relation_get(unit=local_unit(), rid=relid)}
|
||||
for unit in related_units(relid):
|
||||
reldata = relation_get(unit=unit, rid=relid)
|
||||
units[unit] = reldata
|
||||
relids[relid] = units
|
||||
rels[reltype] = relids
|
||||
return rels
|
||||
|
||||
|
||||
def open_port(port, protocol="TCP"):
|
||||
"Open a service network port"
|
||||
_args = ['open-port']
|
||||
_args.append('{}/{}'.format(port, protocol))
|
||||
subprocess.check_call(_args)
|
||||
|
||||
|
||||
def close_port(port, protocol="TCP"):
|
||||
"Close a service network port"
|
||||
_args = ['close-port']
|
||||
_args.append('{}/{}'.format(port, protocol))
|
||||
subprocess.check_call(_args)
|
||||
|
||||
|
||||
@cached
|
||||
def unit_get(attribute):
|
||||
_args = ['unit-get', '--format=json', attribute]
|
||||
try:
|
||||
return json.loads(subprocess.check_output(_args))
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def unit_private_ip():
|
||||
return unit_get('private-address')
|
||||
|
||||
|
||||
class UnregisteredHookError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Hooks(object):
|
||||
def __init__(self):
|
||||
super(Hooks, self).__init__()
|
||||
self._hooks = {}
|
||||
|
||||
def register(self, name, function):
|
||||
self._hooks[name] = function
|
||||
|
||||
def execute(self, args):
|
||||
hook_name = os.path.basename(args[0])
|
||||
if hook_name in self._hooks:
|
||||
self._hooks[hook_name]()
|
||||
else:
|
||||
raise UnregisteredHookError(hook_name)
|
||||
|
||||
def hook(self, *hook_names):
|
||||
def wrapper(decorated):
|
||||
for hook_name in hook_names:
|
||||
self.register(hook_name, decorated)
|
||||
else:
|
||||
self.register(decorated.__name__, decorated)
|
||||
if '_' in decorated.__name__:
|
||||
self.register(
|
||||
decorated.__name__.replace('_', '-'), decorated)
|
||||
return decorated
|
||||
return wrapper
|
||||
|
||||
|
||||
def charm_dir():
|
||||
return os.environ.get('CHARM_DIR')
|
|
@ -0,0 +1,241 @@
|
|||
"""Tools for working with the host system"""
|
||||
# Copyright 2012 Canonical Ltd.
|
||||
#
|
||||
# Authors:
|
||||
# Nick Moffitt <nick.moffitt@canonical.com>
|
||||
# Matthew Wedgwood <matthew.wedgwood@canonical.com>
|
||||
|
||||
import os
|
||||
import pwd
|
||||
import grp
|
||||
import random
|
||||
import string
|
||||
import subprocess
|
||||
import hashlib
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from hookenv import log
|
||||
|
||||
|
||||
def service_start(service_name):
|
||||
return service('start', service_name)
|
||||
|
||||
|
||||
def service_stop(service_name):
|
||||
return service('stop', service_name)
|
||||
|
||||
|
||||
def service_restart(service_name):
|
||||
return service('restart', service_name)
|
||||
|
||||
|
||||
def service_reload(service_name, restart_on_failure=False):
|
||||
service_result = service('reload', service_name)
|
||||
if not service_result and restart_on_failure:
|
||||
service_result = service('restart', service_name)
|
||||
return service_result
|
||||
|
||||
|
||||
def service(action, service_name):
|
||||
cmd = ['service', service_name, action]
|
||||
return subprocess.call(cmd) == 0
|
||||
|
||||
|
||||
def service_running(service):
|
||||
try:
|
||||
output = subprocess.check_output(['service', service, 'status'])
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
else:
|
||||
if ("start/running" in output or "is running" in output):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def adduser(username, password=None, shell='/bin/bash', system_user=False):
|
||||
"""Add a user"""
|
||||
try:
|
||||
user_info = pwd.getpwnam(username)
|
||||
log('user {0} already exists!'.format(username))
|
||||
except KeyError:
|
||||
log('creating user {0}'.format(username))
|
||||
cmd = ['useradd']
|
||||
if system_user or password is None:
|
||||
cmd.append('--system')
|
||||
else:
|
||||
cmd.extend([
|
||||
'--create-home',
|
||||
'--shell', shell,
|
||||
'--password', password,
|
||||
])
|
||||
cmd.append(username)
|
||||
subprocess.check_call(cmd)
|
||||
user_info = pwd.getpwnam(username)
|
||||
return user_info
|
||||
|
||||
|
||||
def add_user_to_group(username, group):
|
||||
"""Add a user to a group"""
|
||||
cmd = [
|
||||
'gpasswd', '-a',
|
||||
username,
|
||||
group
|
||||
]
|
||||
log("Adding user {} to group {}".format(username, group))
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
|
||||
def rsync(from_path, to_path, flags='-r', options=None):
|
||||
"""Replicate the contents of a path"""
|
||||
options = options or ['--delete', '--executability']
|
||||
cmd = ['/usr/bin/rsync', flags]
|
||||
cmd.extend(options)
|
||||
cmd.append(from_path)
|
||||
cmd.append(to_path)
|
||||
log(" ".join(cmd))
|
||||
return subprocess.check_output(cmd).strip()
|
||||
|
||||
|
||||
def symlink(source, destination):
|
||||
"""Create a symbolic link"""
|
||||
log("Symlinking {} as {}".format(source, destination))
|
||||
cmd = [
|
||||
'ln',
|
||||
'-sf',
|
||||
source,
|
||||
destination,
|
||||
]
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
|
||||
def mkdir(path, owner='root', group='root', perms=0555, force=False):
|
||||
"""Create a directory"""
|
||||
log("Making dir {} {}:{} {:o}".format(path, owner, group,
|
||||
perms))
|
||||
uid = pwd.getpwnam(owner).pw_uid
|
||||
gid = grp.getgrnam(group).gr_gid
|
||||
realpath = os.path.abspath(path)
|
||||
if os.path.exists(realpath):
|
||||
if force and not os.path.isdir(realpath):
|
||||
log("Removing non-directory file {} prior to mkdir()".format(path))
|
||||
os.unlink(realpath)
|
||||
else:
|
||||
os.makedirs(realpath, perms)
|
||||
os.chown(realpath, uid, gid)
|
||||
|
||||
|
||||
def write_file(path, content, owner='root', group='root', perms=0444):
|
||||
"""Create or overwrite a file with the contents of a string"""
|
||||
log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))
|
||||
uid = pwd.getpwnam(owner).pw_uid
|
||||
gid = grp.getgrnam(group).gr_gid
|
||||
with open(path, 'w') as target:
|
||||
os.fchown(target.fileno(), uid, gid)
|
||||
os.fchmod(target.fileno(), perms)
|
||||
target.write(content)
|
||||
|
||||
|
||||
def mount(device, mountpoint, options=None, persist=False):
|
||||
'''Mount a filesystem'''
|
||||
cmd_args = ['mount']
|
||||
if options is not None:
|
||||
cmd_args.extend(['-o', options])
|
||||
cmd_args.extend([device, mountpoint])
|
||||
try:
|
||||
subprocess.check_output(cmd_args)
|
||||
except subprocess.CalledProcessError, e:
|
||||
log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
|
||||
return False
|
||||
if persist:
|
||||
# TODO: update fstab
|
||||
pass
|
||||
return True
|
||||
|
||||
|
||||
def umount(mountpoint, persist=False):
|
||||
'''Unmount a filesystem'''
|
||||
cmd_args = ['umount', mountpoint]
|
||||
try:
|
||||
subprocess.check_output(cmd_args)
|
||||
except subprocess.CalledProcessError, e:
|
||||
log('Error unmounting {}\n{}'.format(mountpoint, e.output))
|
||||
return False
|
||||
if persist:
|
||||
# TODO: update fstab
|
||||
pass
|
||||
return True
|
||||
|
||||
|
||||
def mounts():
|
||||
'''List of all mounted volumes as [[mountpoint,device],[...]]'''
|
||||
with open('/proc/mounts') as f:
|
||||
# [['/mount/point','/dev/path'],[...]]
|
||||
system_mounts = [m[1::-1] for m in [l.strip().split()
|
||||
for l in f.readlines()]]
|
||||
return system_mounts
|
||||
|
||||
|
||||
def file_hash(path):
|
||||
''' Generate a md5 hash of the contents of 'path' or None if not found '''
|
||||
if os.path.exists(path):
|
||||
h = hashlib.md5()
|
||||
with open(path, 'r') as source:
|
||||
h.update(source.read()) # IGNORE:E1101 - it does have update
|
||||
return h.hexdigest()
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def restart_on_change(restart_map):
|
||||
''' Restart services based on configuration files changing
|
||||
|
||||
This function is used a decorator, for example
|
||||
|
||||
@restart_on_change({
|
||||
'/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]
|
||||
})
|
||||
def ceph_client_changed():
|
||||
...
|
||||
|
||||
In this example, the cinder-api and cinder-volume services
|
||||
would be restarted if /etc/ceph/ceph.conf is changed by the
|
||||
ceph_client_changed function.
|
||||
'''
|
||||
def wrap(f):
|
||||
def wrapped_f(*args):
|
||||
checksums = {}
|
||||
for path in restart_map:
|
||||
checksums[path] = file_hash(path)
|
||||
f(*args)
|
||||
restarts = []
|
||||
for path in restart_map:
|
||||
if checksums[path] != file_hash(path):
|
||||
restarts += restart_map[path]
|
||||
for service_name in list(OrderedDict.fromkeys(restarts)):
|
||||
service('restart', service_name)
|
||||
return wrapped_f
|
||||
return wrap
|
||||
|
||||
|
||||
def lsb_release():
|
||||
'''Return /etc/lsb-release in a dict'''
|
||||
d = {}
|
||||
with open('/etc/lsb-release', 'r') as lsb:
|
||||
for l in lsb:
|
||||
k, v = l.split('=')
|
||||
d[k.strip()] = v.strip()
|
||||
return d
|
||||
|
||||
|
||||
def pwgen(length=None):
|
||||
'''Generate a random pasword.'''
|
||||
if length is None:
|
||||
length = random.choice(range(35, 45))
|
||||
alphanumeric_chars = [
|
||||
l for l in (string.letters + string.digits)
|
||||
if l not in 'l0QD1vAEIOUaeiou']
|
||||
random_chars = [
|
||||
random.choice(alphanumeric_chars) for _ in range(length)]
|
||||
return(''.join(random_chars))
|
|
@ -0,0 +1,209 @@
|
|||
import importlib
|
||||
from yaml import safe_load
|
||||
from charmhelpers.core.host import (
|
||||
lsb_release
|
||||
)
|
||||
from urlparse import (
|
||||
urlparse,
|
||||
urlunparse,
|
||||
)
|
||||
import subprocess
|
||||
from charmhelpers.core.hookenv import (
|
||||
config,
|
||||
log,
|
||||
)
|
||||
import apt_pkg
|
||||
|
||||
CLOUD_ARCHIVE = """# Ubuntu Cloud Archive
|
||||
deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
|
||||
"""
|
||||
PROPOSED_POCKET = """# Proposed
|
||||
deb http://archive.ubuntu.com/ubuntu {}-proposed main universe multiverse restricted
|
||||
"""
|
||||
|
||||
|
||||
def filter_installed_packages(packages):
|
||||
"""Returns a list of packages that require installation"""
|
||||
apt_pkg.init()
|
||||
cache = apt_pkg.Cache()
|
||||
_pkgs = []
|
||||
for package in packages:
|
||||
try:
|
||||
p = cache[package]
|
||||
p.current_ver or _pkgs.append(package)
|
||||
except KeyError:
|
||||
log('Package {} has no installation candidate.'.format(package),
|
||||
level='WARNING')
|
||||
_pkgs.append(package)
|
||||
return _pkgs
|
||||
|
||||
|
||||
def apt_install(packages, options=None, fatal=False):
|
||||
"""Install one or more packages"""
|
||||
options = options or []
|
||||
cmd = ['apt-get', '-y']
|
||||
cmd.extend(options)
|
||||
cmd.append('install')
|
||||
if isinstance(packages, basestring):
|
||||
cmd.append(packages)
|
||||
else:
|
||||
cmd.extend(packages)
|
||||
log("Installing {} with options: {}".format(packages,
|
||||
options))
|
||||
if fatal:
|
||||
subprocess.check_call(cmd)
|
||||
else:
|
||||
subprocess.call(cmd)
|
||||
|
||||
|
||||
def apt_update(fatal=False):
|
||||
"""Update local apt cache"""
|
||||
cmd = ['apt-get', 'update']
|
||||
if fatal:
|
||||
subprocess.check_call(cmd)
|
||||
else:
|
||||
subprocess.call(cmd)
|
||||
|
||||
|
||||
def apt_purge(packages, fatal=False):
|
||||
"""Purge one or more packages"""
|
||||
cmd = ['apt-get', '-y', 'purge']
|
||||
if isinstance(packages, basestring):
|
||||
cmd.append(packages)
|
||||
else:
|
||||
cmd.extend(packages)
|
||||
log("Purging {}".format(packages))
|
||||
if fatal:
|
||||
subprocess.check_call(cmd)
|
||||
else:
|
||||
subprocess.call(cmd)
|
||||
|
||||
|
||||
def add_source(source, key=None):
|
||||
if ((source.startswith('ppa:') or
|
||||
source.startswith('http:'))):
|
||||
subprocess.check_call(['add-apt-repository', '--yes', source])
|
||||
elif source.startswith('cloud:'):
|
||||
apt_install(filter_installed_packages(['ubuntu-cloud-keyring']),
|
||||
fatal=True)
|
||||
pocket = source.split(':')[-1]
|
||||
with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt:
|
||||
apt.write(CLOUD_ARCHIVE.format(pocket))
|
||||
elif source == 'proposed':
|
||||
release = lsb_release()['DISTRIB_CODENAME']
|
||||
with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt:
|
||||
apt.write(PROPOSED_POCKET.format(release))
|
||||
if key:
|
||||
subprocess.check_call(['apt-key', 'import', key])
|
||||
|
||||
|
||||
class SourceConfigError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def configure_sources(update=False,
|
||||
sources_var='install_sources',
|
||||
keys_var='install_keys'):
|
||||
"""
|
||||
Configure multiple sources from charm configuration
|
||||
|
||||
Example config:
|
||||
install_sources:
|
||||
- "ppa:foo"
|
||||
- "http://example.com/repo precise main"
|
||||
install_keys:
|
||||
- null
|
||||
- "a1b2c3d4"
|
||||
|
||||
Note that 'null' (a.k.a. None) should not be quoted.
|
||||
"""
|
||||
sources = safe_load(config(sources_var))
|
||||
keys = safe_load(config(keys_var))
|
||||
if isinstance(sources, basestring) and isinstance(keys, basestring):
|
||||
add_source(sources, keys)
|
||||
else:
|
||||
if not len(sources) == len(keys):
|
||||
msg = 'Install sources and keys lists are different lengths'
|
||||
raise SourceConfigError(msg)
|
||||
for src_num in range(len(sources)):
|
||||
add_source(sources[src_num], keys[src_num])
|
||||
if update:
|
||||
apt_update(fatal=True)
|
||||
|
||||
# The order of this list is very important. Handlers should be listed in from
|
||||
# least- to most-specific URL matching.
|
||||
FETCH_HANDLERS = (
|
||||
'charmhelpers.fetch.archiveurl.ArchiveUrlFetchHandler',
|
||||
'charmhelpers.fetch.bzrurl.BzrUrlFetchHandler',
|
||||
)
|
||||
|
||||
|
||||
class UnhandledSource(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def install_remote(source):
|
||||
"""
|
||||
Install a file tree from a remote source
|
||||
|
||||
The specified source should be a url of the form:
|
||||
scheme://[host]/path[#[option=value][&...]]
|
||||
|
||||
Schemes supported are based on this modules submodules
|
||||
Options supported are submodule-specific"""
|
||||
# We ONLY check for True here because can_handle may return a string
|
||||
# explaining why it can't handle a given source.
|
||||
handlers = [h for h in plugins() if h.can_handle(source) is True]
|
||||
installed_to = None
|
||||
for handler in handlers:
|
||||
try:
|
||||
installed_to = handler.install(source)
|
||||
except UnhandledSource:
|
||||
pass
|
||||
if not installed_to:
|
||||
raise UnhandledSource("No handler found for source {}".format(source))
|
||||
return installed_to
|
||||
|
||||
|
||||
def install_from_config(config_var_name):
|
||||
charm_config = config()
|
||||
source = charm_config[config_var_name]
|
||||
return install_remote(source)
|
||||
|
||||
|
||||
class BaseFetchHandler(object):
|
||||
"""Base class for FetchHandler implementations in fetch plugins"""
|
||||
def can_handle(self, source):
|
||||
"""Returns True if the source can be handled. Otherwise returns
|
||||
a string explaining why it cannot"""
|
||||
return "Wrong source type"
|
||||
|
||||
def install(self, source):
|
||||
"""Try to download and unpack the source. Return the path to the
|
||||
unpacked files or raise UnhandledSource."""
|
||||
raise UnhandledSource("Wrong source type {}".format(source))
|
||||
|
||||
def parse_url(self, url):
|
||||
return urlparse(url)
|
||||
|
||||
def base_url(self, url):
|
||||
"""Return url without querystring or fragment"""
|
||||
parts = list(self.parse_url(url))
|
||||
parts[4:] = ['' for i in parts[4:]]
|
||||
return urlunparse(parts)
|
||||
|
||||
|
||||
def plugins(fetch_handlers=None):
|
||||
if not fetch_handlers:
|
||||
fetch_handlers = FETCH_HANDLERS
|
||||
plugin_list = []
|
||||
for handler_name in fetch_handlers:
|
||||
package, classname = handler_name.rsplit('.', 1)
|
||||
try:
|
||||
handler_class = getattr(importlib.import_module(package), classname)
|
||||
plugin_list.append(handler_class())
|
||||
except (ImportError, AttributeError):
|
||||
# Skip missing plugins so that they can be ommitted from
|
||||
# installation if desired
|
||||
log("FetchHandler {} not found, skipping plugin".format(handler_name))
|
||||
return plugin_list
|
|
@ -0,0 +1,48 @@
|
|||
import os
|
||||
import urllib2
|
||||
from charmhelpers.fetch import (
|
||||
BaseFetchHandler,
|
||||
UnhandledSource
|
||||
)
|
||||
from charmhelpers.payload.archive import (
|
||||
get_archive_handler,
|
||||
extract,
|
||||
)
|
||||
from charmhelpers.core.host import mkdir
|
||||
|
||||
|
||||
class ArchiveUrlFetchHandler(BaseFetchHandler):
|
||||
"""Handler for archives via generic URLs"""
|
||||
def can_handle(self, source):
|
||||
url_parts = self.parse_url(source)
|
||||
if url_parts.scheme not in ('http', 'https', 'ftp', 'file'):
|
||||
return "Wrong source type"
|
||||
if get_archive_handler(self.base_url(source)):
|
||||
return True
|
||||
return False
|
||||
|
||||
def download(self, source, dest):
|
||||
# propogate all exceptions
|
||||
# URLError, OSError, etc
|
||||
response = urllib2.urlopen(source)
|
||||
try:
|
||||
with open(dest, 'w') as dest_file:
|
||||
dest_file.write(response.read())
|
||||
except Exception as e:
|
||||
if os.path.isfile(dest):
|
||||
os.unlink(dest)
|
||||
raise e
|
||||
|
||||
def install(self, source):
|
||||
url_parts = self.parse_url(source)
|
||||
dest_dir = os.path.join(os.environ.get('CHARM_DIR'), 'fetched')
|
||||
if not os.path.exists(dest_dir):
|
||||
mkdir(dest_dir, perms=0755)
|
||||
dld_file = os.path.join(dest_dir, os.path.basename(url_parts.path))
|
||||
try:
|
||||
self.download(source, dld_file)
|
||||
except urllib2.URLError as e:
|
||||
raise UnhandledSource(e.reason)
|
||||
except OSError as e:
|
||||
raise UnhandledSource(e.strerror)
|
||||
return extract(dld_file)
|
|
@ -0,0 +1,49 @@
|
|||
import os
|
||||
from charmhelpers.fetch import (
|
||||
BaseFetchHandler,
|
||||
UnhandledSource
|
||||
)
|
||||
from charmhelpers.core.host import mkdir
|
||||
|
||||
try:
|
||||
from bzrlib.branch import Branch
|
||||
except ImportError:
|
||||
from charmhelpers.fetch import apt_install
|
||||
apt_install("python-bzrlib")
|
||||
from bzrlib.branch import Branch
|
||||
|
||||
class BzrUrlFetchHandler(BaseFetchHandler):
|
||||
"""Handler for bazaar branches via generic and lp URLs"""
|
||||
def can_handle(self, source):
|
||||
url_parts = self.parse_url(source)
|
||||
if url_parts.scheme not in ('bzr+ssh', 'lp'):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def branch(self, source, dest):
|
||||
url_parts = self.parse_url(source)
|
||||
# If we use lp:branchname scheme we need to load plugins
|
||||
if not self.can_handle(source):
|
||||
raise UnhandledSource("Cannot handle {}".format(source))
|
||||
if url_parts.scheme == "lp":
|
||||
from bzrlib.plugin import load_plugins
|
||||
load_plugins()
|
||||
try:
|
||||
remote_branch = Branch.open(source)
|
||||
remote_branch.bzrdir.sprout(dest).open_branch()
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def install(self, source):
|
||||
url_parts = self.parse_url(source)
|
||||
branch_name = url_parts.path.strip("/").split("/")[-1]
|
||||
dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched", branch_name)
|
||||
if not os.path.exists(dest_dir):
|
||||
mkdir(dest_dir, perms=0755)
|
||||
try:
|
||||
self.branch(source, dest_dir)
|
||||
except OSError as e:
|
||||
raise UnhandledSource(e.strerror)
|
||||
return dest_dir
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import sys
|
||||
import time
|
||||
import os
|
||||
import utils
|
||||
import ceilometer_utils
|
||||
|
||||
|
||||
def install():
|
||||
utils.configure_source()
|
||||
utils.install(*ceilometer_utils.CEILOMETER_AGENT_PACKAGES)
|
||||
ceilometer_utils.modify_config_file(ceilometer_utils.NOVA_CONF,
|
||||
ceilometer_utils.NOVA_SETTINGS)
|
||||
|
||||
port = ceilometer_utils.CEILOMETER_PORT
|
||||
utils.expose(port)
|
||||
|
||||
|
||||
def get_conf():
|
||||
for relid in utils.relation_ids('ceilometer-service'):
|
||||
for unit in utils.relation_list(relid):
|
||||
conf = {
|
||||
"rabbit_host": utils.relation_get('rabbit_host', unit, relid),
|
||||
"rabbit_virtual_host": ceilometer_utils.RABBIT_VHOST,
|
||||
"rabbit_userid": ceilometer_utils.RABBIT_USER,
|
||||
"rabbit_password": utils.relation_get('rabbit_password', unit, relid),
|
||||
"keystone_os_username": utils.relation_get('keystone_os_username', unit, relid),
|
||||
"keystone_os_password": utils.relation_get('keystone_os_password', unit, relid),
|
||||
"keystone_os_tenant": utils.relation_get('keystone_os_tenant', unit, relid),
|
||||
"keystone_host": utils.relation_get('keystone_host', unit, relid),
|
||||
"keystone_port": utils.relation_get('keystone_port', unit, relid),
|
||||
"metering_secret": utils.relation_get('metering_secret', unit, relid)
|
||||
}
|
||||
if None not in conf.itervalues():
|
||||
return conf
|
||||
return None
|
||||
|
||||
def render_ceilometer_conf(context):
|
||||
if (context and os.path.exists(ceilometer_utils.CEILOMETER_CONF)):
|
||||
# merge contexts
|
||||
context['service_port'] = ceilometer_utils.CEILOMETER_PORT
|
||||
context['ceilometer_host'] = utils.get_unit_hostname()
|
||||
|
||||
with open(ceilometer_utils.CEILOMETER_CONF, "w") as conf:
|
||||
conf.write(utils.render_template(
|
||||
os.path.basename(ceilometer_utils.CEILOMETER_CONF), context))
|
||||
|
||||
utils.restart(*ceilometer_utils.CEILOMETER_COMPUTE_SERVICES)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def ceilometer_changed():
|
||||
# check if we have rabbit and keystone already set
|
||||
context = get_conf()
|
||||
if context:
|
||||
render_ceilometer_conf(context)
|
||||
else:
|
||||
# still waiting
|
||||
utils.juju_log("INFO", "ceilometer: rabbit and keystone " +
|
||||
"credentials not yet received from peer.")
|
||||
|
||||
|
||||
utils.do_hooks({
|
||||
"install": install,
|
||||
"ceilometer-service-relation-changed": ceilometer_changed
|
||||
})
|
||||
sys.exit(0)
|
|
@ -1 +1 @@
|
|||
hooks.py
|
||||
ceilometer_hooks.py
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -1,194 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# Common python helper functions used for OpenStack charms.
|
||||
|
||||
import subprocess
|
||||
|
||||
CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu"
|
||||
CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA'
|
||||
|
||||
ubuntu_openstack_release = {
|
||||
'oneiric': 'diablo',
|
||||
'precise': 'essex',
|
||||
'quantal': 'folsom',
|
||||
'raring': 'grizzly'
|
||||
}
|
||||
|
||||
|
||||
openstack_codenames = {
|
||||
'2011.2': 'diablo',
|
||||
'2012.1': 'essex',
|
||||
'2012.2': 'folsom',
|
||||
'2012.3': 'grizzly'
|
||||
}
|
||||
|
||||
|
||||
def juju_log(msg):
|
||||
subprocess.check_call(['juju-log', msg])
|
||||
|
||||
|
||||
def error_out(msg):
|
||||
juju_log("FATAL ERROR: %s" % msg)
|
||||
exit(1)
|
||||
|
||||
|
||||
def lsb_release():
|
||||
'''Return /etc/lsb-release in a dict'''
|
||||
lsb = open('/etc/lsb-release', 'r')
|
||||
d = {}
|
||||
for l in lsb:
|
||||
k, v = l.split('=')
|
||||
d[k.strip()] = v.strip()
|
||||
return d
|
||||
|
||||
|
||||
def get_os_codename_install_source(src):
|
||||
'''Derive OpenStack release codename from a given installation source.'''
|
||||
ubuntu_rel = lsb_release()['DISTRIB_CODENAME']
|
||||
|
||||
rel = ''
|
||||
if src == 'distro':
|
||||
try:
|
||||
rel = ubuntu_openstack_release[ubuntu_rel]
|
||||
except KeyError:
|
||||
e = 'Code not derive openstack release for '\
|
||||
'this Ubuntu release: %s' % rel
|
||||
error_out(e)
|
||||
return rel
|
||||
|
||||
if src.startswith('cloud:'):
|
||||
ca_rel = src.split(':')[1]
|
||||
ca_rel = ca_rel.split('%s-' % ubuntu_rel)[1].split('/')[0]
|
||||
return ca_rel
|
||||
|
||||
# Best guess match based on deb string provided
|
||||
if src.startswith('deb'):
|
||||
for k, v in openstack_codenames.iteritems():
|
||||
if v in src:
|
||||
return v
|
||||
|
||||
|
||||
def get_os_codename_version(vers):
|
||||
'''Determine OpenStack codename from version number.'''
|
||||
try:
|
||||
return openstack_codenames[vers]
|
||||
except KeyError:
|
||||
e = 'Could not determine OpenStack codename for version %s' % vers
|
||||
error_out(e)
|
||||
|
||||
|
||||
def get_os_version_codename(codename):
|
||||
'''Determine OpenStack version number from codename.'''
|
||||
for k, v in openstack_codenames.iteritems():
|
||||
if v == codename:
|
||||
return k
|
||||
e = 'Code not derive OpenStack version for '\
|
||||
'codename: %s' % codename
|
||||
error_out(e)
|
||||
|
||||
|
||||
def get_os_codename_package(pkg):
|
||||
'''Derive OpenStack release codename from an installed package.'''
|
||||
cmd = ['dpkg', '-l', pkg]
|
||||
|
||||
try:
|
||||
output = subprocess.check_output(cmd)
|
||||
except subprocess.CalledProcessError:
|
||||
e = 'Could not derive OpenStack version from package that is not '\
|
||||
'installed; %s' % pkg
|
||||
error_out(e)
|
||||
|
||||
def _clean(line):
|
||||
line = line.split(' ')
|
||||
clean = []
|
||||
for c in line:
|
||||
if c != '':
|
||||
clean.append(c)
|
||||
return clean
|
||||
|
||||
vers = None
|
||||
for l in output.split('\n'):
|
||||
if l.startswith('ii'):
|
||||
l = _clean(l)
|
||||
if l[1] == pkg:
|
||||
vers = l[2]
|
||||
|
||||
if not vers:
|
||||
e = 'Could not determine version of installed package: %s' % pkg
|
||||
error_out(e)
|
||||
|
||||
vers = vers[:6]
|
||||
try:
|
||||
return openstack_codenames[vers]
|
||||
except KeyError:
|
||||
e = 'Could not determine OpenStack codename for version %s' % vers
|
||||
error_out(e)
|
||||
|
||||
|
||||
def configure_installation_source(rel):
|
||||
'''Configure apt installation source.'''
|
||||
|
||||
def _import_key(id):
|
||||
cmd = "apt-key adv --keyserver keyserver.ubuntu.com " \
|
||||
"--recv-keys %s" % id
|
||||
try:
|
||||
subprocess.check_call(cmd.split(' '))
|
||||
except:
|
||||
error_out("Error importing repo key %s" % id)
|
||||
|
||||
if rel == 'distro':
|
||||
return
|
||||
elif rel[:4] == "ppa:":
|
||||
src = rel
|
||||
subprocess.check_call(["add-apt-repository", "-y", src])
|
||||
elif rel[:3] == "deb":
|
||||
l = len(rel.split('|'))
|
||||
if l == 2:
|
||||
src, key = rel.split('|')
|
||||
juju_log("Importing PPA key from keyserver for %s" % src)
|
||||
_import_key(key)
|
||||
elif l == 1:
|
||||
src = rel
|
||||
else:
|
||||
error_out("Invalid openstack-release: %s" % rel)
|
||||
|
||||
with open('/etc/apt/sources.list.d/juju_deb.list', 'w') as f:
|
||||
f.write(src)
|
||||
elif rel[:6] == 'cloud:':
|
||||
ubuntu_rel = lsb_release()['DISTRIB_CODENAME']
|
||||
rel = rel.split(':')[1]
|
||||
u_rel = rel.split('-')[0]
|
||||
ca_rel = rel.split('-')[1]
|
||||
|
||||
if u_rel != ubuntu_rel:
|
||||
e = 'Cannot install from Cloud Archive pocket %s on this Ubuntu '\
|
||||
'version (%s)' % (ca_rel, ubuntu_rel)
|
||||
error_out(e)
|
||||
|
||||
if ca_rel == 'folsom/staging':
|
||||
# staging is just a regular PPA.
|
||||
cmd = 'add-apt-repository -y '\
|
||||
'ppa:ubuntu-cloud-archive/folsom-staging'
|
||||
subprocess.check_call(cmd.split(' '))
|
||||
return
|
||||
|
||||
# map charm config options to actual archive pockets.
|
||||
pockets = {
|
||||
'folsom': 'precise-updates/folsom',
|
||||
'folsom/updates': 'precise-updates/folsom',
|
||||
'folsom/proposed': 'precise-proposed/folsom'
|
||||
}
|
||||
|
||||
try:
|
||||
pocket = pockets[ca_rel]
|
||||
except KeyError:
|
||||
e = 'Invalid Cloud Archive release specified: %s' % rel
|
||||
error_out(e)
|
||||
|
||||
src = "deb %s %s main" % (CLOUD_ARCHIVE_URL, pocket)
|
||||
_import_key(CLOUD_ARCHIVE_KEY_ID)
|
||||
|
||||
with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as f:
|
||||
f.write(src)
|
||||
else:
|
||||
error_out("Invalid openstack-release specified: %s" % rel)
|
239
hooks/utils.py
239
hooks/utils.py
|
@ -1,239 +0,0 @@
|
|||
#
|
||||
# Copyright 2012 Canonical Ltd.
|
||||
#
|
||||
# Authors:
|
||||
# James Page <james.page@ubuntu.com>
|
||||
# Paul Collins <paul.collins@canonical.com>
|
||||
#
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import socket
|
||||
import sys
|
||||
import apt_pkg as apt
|
||||
import re
|
||||
import ceilometer_utils
|
||||
|
||||
|
||||
def do_hooks(hooks):
|
||||
hook = os.path.basename(sys.argv[0])
|
||||
|
||||
try:
|
||||
hook_func = hooks[hook]
|
||||
except KeyError:
|
||||
juju_log('INFO',
|
||||
"This charm doesn't know how to handle '{}'.".format(hook))
|
||||
else:
|
||||
hook_func()
|
||||
|
||||
|
||||
def install(*pkgs):
|
||||
cmd = [
|
||||
'apt-get',
|
||||
'-y',
|
||||
'install'
|
||||
]
|
||||
for pkg in pkgs:
|
||||
cmd.append(pkg)
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
TEMPLATES_DIR = 'templates'
|
||||
|
||||
try:
|
||||
import jinja2
|
||||
except ImportError:
|
||||
install('python-jinja2')
|
||||
import jinja2
|
||||
|
||||
|
||||
def render_template(template_name, context, template_dir=TEMPLATES_DIR):
|
||||
templates = jinja2.Environment(
|
||||
loader=jinja2.FileSystemLoader(template_dir)
|
||||
)
|
||||
template = templates.get_template(template_name)
|
||||
return template.render(context)
|
||||
|
||||
CLOUD_ARCHIVE = \
|
||||
""" # Ubuntu Cloud Archive
|
||||
deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
|
||||
"""
|
||||
|
||||
CLOUD_ARCHIVE_POCKETS = {
|
||||
'precise-folsom': 'precise-updates/folsom',
|
||||
'precise-folsom/updates': 'precise-updates/folsom',
|
||||
'precise-folsom/proposed': 'precise-proposed/folsom',
|
||||
'precise-grizzly': 'precise-updates/grizzly',
|
||||
'precise-grizzly/updates': 'precise-updates/grizzly',
|
||||
'precise-grizzly/proposed': 'precise-proposed/grizzly'
|
||||
}
|
||||
|
||||
|
||||
def configure_source():
|
||||
source = str(config_get('openstack-origin'))
|
||||
if not source:
|
||||
return
|
||||
if source.startswith('ppa:'):
|
||||
cmd = [
|
||||
'add-apt-repository',
|
||||
source
|
||||
]
|
||||
subprocess.check_call(cmd)
|
||||
if source.startswith('cloud:'):
|
||||
install('ubuntu-cloud-keyring')
|
||||
pocket = source.split(':')[1]
|
||||
with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt:
|
||||
apt.write(CLOUD_ARCHIVE.format(CLOUD_ARCHIVE_POCKETS[pocket]))
|
||||
if source.startswith('deb'):
|
||||
l = len(source.split('|'))
|
||||
if l == 2:
|
||||
(apt_line, key) = source.split('|')
|
||||
cmd = [
|
||||
'apt-key',
|
||||
'adv', '--keyserver keyserver.ubuntu.com',
|
||||
'--recv-keys', key
|
||||
]
|
||||
subprocess.check_call(cmd)
|
||||
elif l == 1:
|
||||
apt_line = source
|
||||
|
||||
with open('/etc/apt/sources.list.d/ceilometer.list', 'w') as apt:
|
||||
apt.write(apt_line + "\n")
|
||||
cmd = [
|
||||
'apt-get',
|
||||
'update'
|
||||
]
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
# Protocols
|
||||
TCP = 'TCP'
|
||||
UDP = 'UDP'
|
||||
|
||||
|
||||
def expose(port, protocol='TCP'):
|
||||
cmd = [
|
||||
'open-port',
|
||||
'{}/{}'.format(port, protocol)
|
||||
]
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
|
||||
def unexpose(port, protocol='TCP'):
|
||||
cmd = [
|
||||
'close-port',
|
||||
'{}/{}'.format(port, protocol)
|
||||
]
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
|
||||
def juju_log(severity, message):
|
||||
cmd = [
|
||||
'juju-log',
|
||||
'--log-level', severity,
|
||||
message
|
||||
]
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
|
||||
def relation_ids(relation):
|
||||
cmd = [
|
||||
'relation-ids',
|
||||
relation
|
||||
]
|
||||
return subprocess.check_output(cmd).split() # IGNORE:E1103
|
||||
|
||||
|
||||
def relation_list(rid):
|
||||
cmd = [
|
||||
'relation-list',
|
||||
'-r', rid,
|
||||
]
|
||||
return subprocess.check_output(cmd).split() # IGNORE:E1103
|
||||
|
||||
|
||||
def relation_get(attribute, unit=None, rid=None):
|
||||
cmd = [
|
||||
'relation-get',
|
||||
]
|
||||
if rid:
|
||||
cmd.append('-r')
|
||||
cmd.append(rid)
|
||||
cmd.append(attribute)
|
||||
if unit:
|
||||
cmd.append(unit)
|
||||
value = subprocess.check_output(cmd).strip() # IGNORE:E1103
|
||||
if value == "":
|
||||
return None
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
def relation_set(**kwargs):
|
||||
cmd = [
|
||||
'relation-set'
|
||||
]
|
||||
args = []
|
||||
for k, v in kwargs.items():
|
||||
if k == 'rid':
|
||||
cmd.append('-r')
|
||||
cmd.append(v)
|
||||
else:
|
||||
args.append('{}={}'.format(k, v))
|
||||
cmd += args
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
|
||||
def unit_get(attribute):
|
||||
cmd = [
|
||||
'unit-get',
|
||||
attribute
|
||||
]
|
||||
value = subprocess.check_output(cmd).strip() # IGNORE:E1103
|
||||
if value == "":
|
||||
return None
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
def config_get(attribute):
|
||||
cmd = [
|
||||
'config-get',
|
||||
attribute
|
||||
]
|
||||
value = subprocess.check_output(cmd).strip() # IGNORE:E1103
|
||||
if value == "":
|
||||
return None
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
def get_unit_hostname():
|
||||
return socket.gethostname()
|
||||
|
||||
|
||||
def _service_ctl(service, action):
|
||||
subprocess.check_call(['service', service, action])
|
||||
|
||||
|
||||
def restart(*services):
|
||||
for service in services:
|
||||
_service_ctl(service, 'restart')
|
||||
|
||||
|
||||
def stop(*services):
|
||||
for service in services:
|
||||
_service_ctl(service, 'stop')
|
||||
|
||||
|
||||
def start(*services):
|
||||
for service in services:
|
||||
_service_ctl(service, 'start')
|
||||
|
||||
|
||||
def get_os_version(package=None):
|
||||
apt.init()
|
||||
cache = apt.Cache()
|
||||
pkg = cache[package or 'quantum-common']
|
||||
if pkg.current_ver:
|
||||
return apt.upstream_version(pkg.current_ver.ver_str)
|
||||
else:
|
||||
return None
|
|
@ -0,0 +1,769 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="96"
|
||||
height="96"
|
||||
id="svg6517"
|
||||
version="1.1"
|
||||
inkscape:version="0.48+devel r12304"
|
||||
sodipodi:docname="Openstack.svg">
|
||||
<defs
|
||||
id="defs6519">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient902">
|
||||
<stop
|
||||
style="stop-color:#cccccc;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop904" />
|
||||
<stop
|
||||
style="stop-color:#e6e6e6;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop906" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="Background">
|
||||
<stop
|
||||
id="stop4178"
|
||||
offset="0"
|
||||
style="stop-color:#22779e;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop4180"
|
||||
offset="1"
|
||||
style="stop-color:#2991c0;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB;"
|
||||
inkscape:label="Inner Shadow"
|
||||
id="filter1121">
|
||||
<feFlood
|
||||
flood-opacity="0.59999999999999998"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
id="feFlood1123" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="out"
|
||||
result="composite1"
|
||||
id="feComposite1125" />
|
||||
<feGaussianBlur
|
||||
in="composite1"
|
||||
stdDeviation="1"
|
||||
result="blur"
|
||||
id="feGaussianBlur1127" />
|
||||
<feOffset
|
||||
dx="0"
|
||||
dy="2"
|
||||
result="offset"
|
||||
id="feOffset1129" />
|
||||
<feComposite
|
||||
in="offset"
|
||||
in2="SourceGraphic"
|
||||
operator="atop"
|
||||
result="composite2"
|
||||
id="feComposite1131" />
|
||||
</filter>
|
||||
<filter
|
||||
style="color-interpolation-filters:sRGB;"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter950">
|
||||
<feFlood
|
||||
flood-opacity="0.25"
|
||||
flood-color="rgb(0,0,0)"
|
||||
result="flood"
|
||||
id="feFlood952" />
|
||||
<feComposite
|
||||
in="flood"
|
||||
in2="SourceGraphic"
|
||||
operator="in"
|
||||
result="composite1"
|
||||
id="feComposite954" />
|
||||
<feGaussianBlur
|
||||
in="composite1"
|
||||
stdDeviation="1"
|
||||
result="blur"
|
||||
id="feGaussianBlur956" />
|
||||
<feOffset
|
||||
dx="0"
|
||||
dy="1"
|
||||
result="offset"
|
||||
id="feOffset958" />
|
||||
<feComposite
|
||||
in="SourceGraphic"
|
||||
in2="offset"
|
||||
operator="over"
|
||||
result="composite2"
|
||||
id="feComposite960" />
|
||||
</filter>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath873">
|
||||
<g
|
||||
transform="matrix(0,-0.66666667,0.66604479,0,-258.25992,677.00001)"
|
||||
id="g875"
|
||||
inkscape:label="Layer 1"
|
||||
style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline">
|
||||
<path
|
||||
style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline"
|
||||
d="m 46.702703,898.22775 50.594594,0 C 138.16216,898.22775 144,904.06497 144,944.92583 l 0,50.73846 c 0,40.86071 -5.83784,46.69791 -46.702703,46.69791 l -50.594594,0 C 5.8378378,1042.3622 0,1036.525 0,995.66429 L 0,944.92583 C 0,904.06497 5.8378378,898.22775 46.702703,898.22775 Z"
|
||||
id="path877"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="sssssssss" />
|
||||
</g>
|
||||
</clipPath>
|
||||
<filter
|
||||
inkscape:collect="always"
|
||||
id="filter891"
|
||||
inkscape:label="Badge Shadow">
|
||||
<feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="0.71999962"
|
||||
id="feGaussianBlur893" />
|
||||
</filter>
|
||||
<style
|
||||
id="style867"
|
||||
type="text/css"><![CDATA[
|
||||
.fil0 {fill:#1F1A17}
|
||||
]]></style>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient902"
|
||||
id="linearGradient908"
|
||||
x1="-220"
|
||||
y1="731.29077"
|
||||
x2="-220"
|
||||
y2="635.29077"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<clipPath
|
||||
id="clipPath16">
|
||||
<path
|
||||
id="path18"
|
||||
d="m -9,-9 614,0 0,231 -614,0 0,-231 z" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
id="clipPath116">
|
||||
<path
|
||||
id="path118"
|
||||
d="m 91.7368,146.3253 -9.7039,-1.577 -8.8548,-3.8814 -7.5206,-4.7308 -7.1566,-8.7335 -4.0431,-4.282 -3.9093,-1.4409 -1.034,2.5271 1.8079,2.6096 0.4062,3.6802 1.211,-0.0488 1.3232,-1.2069 -0.3569,3.7488 -1.4667,0.9839 0.0445,1.4286 -3.4744,-1.9655 -3.1462,-3.712 -0.6559,-3.3176 1.3453,-2.6567 1.2549,-4.5133 2.5521,-1.2084 2.6847,0.1318 2.5455,1.4791 -1.698,-8.6122 1.698,-9.5825 -1.8692,-4.4246 -6.1223,-6.5965 1.0885,-3.941 2.9002,-4.5669 5.4688,-3.8486 2.9007,-0.3969 3.225,-0.1094 -2.012,-8.2601 7.3993,-3.0326 9.2188,-1.2129 3.1535,2.0619 0.2427,5.5797 3.5178,5.8224 0.2426,4.6094 8.4909,-0.6066 7.8843,0.7279 -7.8843,-4.7307 1.3343,-5.701 4.9731,-7.763 4.8521,-2.0622 3.8814,1.5769 1.577,3.1538 8.1269,6.1861 1.5769,-1.3343 12.7363,-0.485 2.5473,2.0619 0.2426,3.6391 -0.849,1.5767 -0.6066,9.8251 -4.2454,8.4909 0.7276,3.7605 2.5475,-1.3343 7.1566,-6.6716 3.5175,-0.2424 3.8815,1.5769 3.8818,2.9109 1.9406,6.3077 11.4021,-0.7277 6.914,2.6686 5.5797,5.2157 4.0028,7.5206 0.9706,8.8546 -0.8493,10.3105 -2.1832,9.2185 -2.1836,2.9112 -3.0322,0.9706 -5.3373,-5.8224 -4.8518,-1.6982 -4.2455,7.0353 -4.2454,3.8815 -2.3049,1.4556 -9.2185,7.6419 -7.3993,4.0028 -7.3993,0.6066 -8.6119,-1.4556 -7.5206,-2.7899 -5.2158,-4.2454 -4.1241,-4.9734 -4.2454,-1.2129" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
id="clipPath128">
|
||||
<path
|
||||
id="path130"
|
||||
d="m 91.7368,146.3253 -9.7039,-1.577 -8.8548,-3.8814 -7.5206,-4.7308 -7.1566,-8.7335 -4.0431,-4.282 -3.9093,-1.4409 -1.034,2.5271 1.8079,2.6096 0.4062,3.6802 1.211,-0.0488 1.3232,-1.2069 -0.3569,3.7488 -1.4667,0.9839 0.0445,1.4286 -3.4744,-1.9655 -3.1462,-3.712 -0.6559,-3.3176 1.3453,-2.6567 1.2549,-4.5133 2.5521,-1.2084 2.6847,0.1318 2.5455,1.4791 -1.698,-8.6122 1.698,-9.5825 -1.8692,-4.4246 -6.1223,-6.5965 1.0885,-3.941 2.9002,-4.5669 5.4688,-3.8486 2.9007,-0.3969 3.225,-0.1094 -2.012,-8.2601 7.3993,-3.0326 9.2188,-1.2129 3.1535,2.0619 0.2427,5.5797 3.5178,5.8224 0.2426,4.6094 8.4909,-0.6066 7.8843,0.7279 -7.8843,-4.7307 1.3343,-5.701 4.9731,-7.763 4.8521,-2.0622 3.8814,1.5769 1.577,3.1538 8.1269,6.1861 1.5769,-1.3343 12.7363,-0.485 2.5473,2.0619 0.2426,3.6391 -0.849,1.5767 -0.6066,9.8251 -4.2454,8.4909 0.7276,3.7605 2.5475,-1.3343 7.1566,-6.6716 3.5175,-0.2424 3.8815,1.5769 3.8818,2.9109 1.9406,6.3077 11.4021,-0.7277 6.914,2.6686 5.5797,5.2157 4.0028,7.5206 0.9706,8.8546 -0.8493,10.3105 -2.1832,9.2185 -2.1836,2.9112 -3.0322,0.9706 -5.3373,-5.8224 -4.8518,-1.6982 -4.2455,7.0353 -4.2454,3.8815 -2.3049,1.4556 -9.2185,7.6419 -7.3993,4.0028 -7.3993,0.6066 -8.6119,-1.4556 -7.5206,-2.7899 -5.2158,-4.2454 -4.1241,-4.9734 -4.2454,-1.2129" />
|
||||
</clipPath>
|
||||
<linearGradient
|
||||
id="linearGradient3850"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop3852"
|
||||
offset="0"
|
||||
style="stop-color:#000000;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop3854"
|
||||
offset="1"
|
||||
style="stop-color:#000000;stop-opacity:0;" />
|
||||
</linearGradient>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath3095">
|
||||
<path
|
||||
d="m 976.648,389.551 -842.402,0 0,839.999 842.402,0 0,-839.999"
|
||||
id="path3097"
|
||||
inkscape:connector-curvature="0" />
|
||||
</clipPath>
|
||||
<linearGradient
|
||||
y2="1005.2372"
|
||||
x2="1128.7266"
|
||||
y1="1031.9298"
|
||||
x1="1155.4192"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient4705"
|
||||
xlink:href="#linearGradient4439"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
id="linearGradient4439"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop4441"
|
||||
offset="0"
|
||||
style="stop-color:#841f1c;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop4443"
|
||||
offset="1"
|
||||
style="stop-color:#c42e24;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
y2="946.60907"
|
||||
x2="753.62018"
|
||||
y1="934.54321"
|
||||
x1="741.55432"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient4707"
|
||||
xlink:href="#linearGradient4473"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
id="linearGradient4473"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop4475"
|
||||
offset="0"
|
||||
style="stop-color:#8d211a;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop4477"
|
||||
offset="1"
|
||||
style="stop-color:#c42e24;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath3195">
|
||||
<path
|
||||
d="m 611.836,756.738 -106.34,105.207 c -8.473,8.289 -13.617,20.102 -13.598,33.379 L 598.301,790.207 c -0.031,-13.418 5.094,-25.031 13.535,-33.469"
|
||||
id="path3197"
|
||||
inkscape:connector-curvature="0" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath3235">
|
||||
<path
|
||||
d="m 1095.64,1501.81 c 35.46,-35.07 70.89,-70.11 106.35,-105.17 4.4,-4.38 7.11,-10.53 7.11,-17.55 l -106.37,105.21 c 0,7 -2.71,13.11 -7.09,17.51"
|
||||
id="path3237"
|
||||
inkscape:connector-curvature="0" />
|
||||
</clipPath>
|
||||
<linearGradient
|
||||
y2="1595.1495"
|
||||
x2="1299.6487"
|
||||
y1="1607.1796"
|
||||
x1="1311.6787"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient4709"
|
||||
xlink:href="#linearGradient4389"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
id="linearGradient4389"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop4391"
|
||||
offset="0"
|
||||
style="stop-color:#912120;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop4393"
|
||||
offset="1"
|
||||
style="stop-color:#c42e24;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
y2="666.30505"
|
||||
x2="903.20746"
|
||||
y1="672.85754"
|
||||
x1="896.65497"
|
||||
gradientTransform="matrix(8,0,0,-8,-6602.8469,6874.4287)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient4711"
|
||||
xlink:href="#linearGradient4355"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
id="linearGradient4355"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop4357"
|
||||
offset="0"
|
||||
style="stop-color:#841f1c;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop4359"
|
||||
offset="1"
|
||||
style="stop-color:#c42e24;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<clipPath
|
||||
id="clipPath4591"
|
||||
clipPathUnits="userSpaceOnUse">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 1106.6009,730.43734 -0.036,21.648 c -0.01,3.50825 -2.8675,6.61375 -6.4037,6.92525 l -83.6503,7.33162 c -3.5205,0.30763 -6.3812,-2.29987 -6.3671,-5.8145 l 0.036,-21.6475 20.1171,-1.76662 -0.011,4.63775 c 0,1.83937 1.4844,3.19925 3.3262,3.0395 l 49.5274,-4.33975 c 1.8425,-0.166 3.3425,-1.78125 3.3538,-3.626 l 0.01,-4.63025 20.1,-1.7575"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path4593" />
|
||||
</clipPath>
|
||||
<filter
|
||||
height="1.3208274"
|
||||
y="-0.16041371"
|
||||
width="1.0564449"
|
||||
x="-0.028222449"
|
||||
id="filter4595"
|
||||
inkscape:collect="always"
|
||||
color-interpolation-filters="sRGB">
|
||||
<feGaussianBlur
|
||||
id="feGaussianBlur4597"
|
||||
stdDeviation="0.66106737"
|
||||
inkscape:collect="always" />
|
||||
</filter>
|
||||
<filter
|
||||
id="filter3831"
|
||||
inkscape:collect="always">
|
||||
<feGaussianBlur
|
||||
id="feGaussianBlur3833"
|
||||
stdDeviation="0.86309522"
|
||||
inkscape:collect="always" />
|
||||
</filter>
|
||||
<radialGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(-1.4333926,-2.2742838,1.1731823,-0.73941125,-174.08025,98.374394)"
|
||||
r="20.40658"
|
||||
fy="93.399292"
|
||||
fx="-26.508606"
|
||||
cy="93.399292"
|
||||
cx="-26.508606"
|
||||
id="radialGradient3856"
|
||||
xlink:href="#linearGradient3850"
|
||||
inkscape:collect="always" />
|
||||
<filter
|
||||
height="1.3286154"
|
||||
y="-0.1643077"
|
||||
width="1.3437241"
|
||||
x="-0.17186206"
|
||||
id="filter3868"
|
||||
inkscape:collect="always">
|
||||
<feGaussianBlur
|
||||
id="feGaussianBlur3870"
|
||||
stdDeviation="0.62628186"
|
||||
inkscape:collect="always" />
|
||||
</filter>
|
||||
<filter
|
||||
id="filter3885"
|
||||
inkscape:collect="always">
|
||||
<feGaussianBlur
|
||||
id="feGaussianBlur3887"
|
||||
stdDeviation="5.7442192"
|
||||
inkscape:collect="always" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
gradientTransform="translate(-318.48033,212.32022)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
y2="993.19702"
|
||||
x2="-51.879555"
|
||||
y1="593.11615"
|
||||
x1="348.20132"
|
||||
id="linearGradient3895"
|
||||
xlink:href="#linearGradient3850"
|
||||
inkscape:collect="always" />
|
||||
<radialGradient
|
||||
r="20.40658"
|
||||
fy="93.399292"
|
||||
fx="-26.508606"
|
||||
cy="93.399292"
|
||||
cx="-26.508606"
|
||||
gradientTransform="matrix(-1.4333926,-2.2742838,1.1731823,-0.73941125,-174.08025,98.374394)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient3902"
|
||||
xlink:href="#linearGradient3850"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
y2="993.19702"
|
||||
x2="-51.879555"
|
||||
y1="593.11615"
|
||||
x1="348.20132"
|
||||
gradientTransform="translate(-318.48033,212.32022)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient3904"
|
||||
xlink:href="#linearGradient3850"
|
||||
inkscape:collect="always" />
|
||||
<clipPath
|
||||
id="clipPath3906"
|
||||
clipPathUnits="userSpaceOnUse">
|
||||
<rect
|
||||
transform="scale(1,-1)"
|
||||
style="opacity:0.8;color:#000000;fill:#ff00ff;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="rect3908"
|
||||
width="1019.1371"
|
||||
height="1019.1371"
|
||||
x="357.9816"
|
||||
y="-1725.8152" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="4.0745361"
|
||||
inkscape:cx="36.786689"
|
||||
inkscape:cy="54.859325"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1029"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1"
|
||||
showborder="true"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:showpageshadow="false"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:snap-bbox-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:snap-midpoints="true"
|
||||
inkscape:snap-object-midpoints="true"
|
||||
inkscape:snap-center="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid821" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="16,48"
|
||||
id="guide823" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="64,80"
|
||||
id="guide825" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="80,40"
|
||||
id="guide827" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="64,16"
|
||||
id="guide829" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata6522">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="BACKGROUND"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(268,-635.29076)"
|
||||
style="display:inline">
|
||||
<path
|
||||
style="fill:url(#linearGradient908);fill-opacity:1;stroke:none;display:inline;filter:url(#filter1121)"
|
||||
d="m -268,700.15563 0,-33.72973 c 0,-27.24324 3.88785,-31.13513 31.10302,-31.13513 l 33.79408,0 c 27.21507,0 31.1029,3.89189 31.1029,31.13513 l 0,33.72973 c 0,27.24325 -3.88783,31.13514 -31.1029,31.13514 l -33.79408,0 C -264.11215,731.29077 -268,727.39888 -268,700.15563 Z"
|
||||
id="path6455"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="sssssssss" />
|
||||
<g
|
||||
transform="matrix(0.72090266,0,0,0.72090267,152.00189,279.53977)"
|
||||
id="layer1-5"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:transform-center-x="34.09445"
|
||||
inkscape:transform-center-y="31.651869">
|
||||
<g
|
||||
clip-path="url(#clipPath3906)"
|
||||
transform="matrix(0.09419734,0,0,-0.09419734,-603.43521,674.35797)"
|
||||
id="g4601">
|
||||
<g
|
||||
transform="translate(647.57667,2.5364216e-8)"
|
||||
id="g3897">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="opacity:0.7;color:#000000;fill:url(#radialGradient3902);fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;filter:url(#filter3831);enable-background:accumulate"
|
||||
d="m -48.09375,67.8125 c -0.873996,-0.0028 -2.089735,0.01993 -3.40625,0.09375 -2.633031,0.147647 -5.700107,0.471759 -7.78125,1.53125 a 1.0001,1.0001 0 0 0 -0.25,1.59375 L -38.8125,92.375 a 1.0001,1.0001 0 0 0 0.84375,0.3125 L -24,90.5625 a 1.0001,1.0001 0 0 0 0.53125,-1.71875 L -46.0625,68.125 a 1.0001,1.0001 0 0 0 -0.625,-0.28125 C -46.6875,67.84375 -47.219754,67.81533 -48.09375,67.8125 Z"
|
||||
transform="matrix(10.616011,0,0,-10.616011,357.98166,1725.8152)"
|
||||
id="path3821" />
|
||||
<path
|
||||
style="opacity:0.6;color:#000000;fill:none;stroke:#000000;stroke-width:2.77429962;stroke-linecap:round;marker:none;visibility:visible;display:inline;overflow:visible;filter:url(#filter3868);enable-background:accumulate"
|
||||
d="m -15.782705,81.725197 8.7458304,9.147937"
|
||||
id="path3858"
|
||||
inkscape:connector-curvature="0"
|
||||
transform="matrix(10.616011,0,0,-10.616011,39.50133,1725.8152)" />
|
||||
<path
|
||||
style="font-size:xx-small;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;opacity:0.3;color:#000000;fill:url(#linearGradient3904);fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;filter:url(#filter3885);enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans"
|
||||
d="m -95.18931,981.03569 a 10.617073,10.617073 0 0 1 -0.995251,-0.3318 l -42.795789,-5.308 a 10.617073,10.617073 0 0 1 -6.30326,-17.9145 L -4.2897203,812.5065 a 10.617073,10.617073 0 0 1 8.95726,-3.3175 l 49.0990503,7.63026 a 10.617073,10.617073 0 0 1 5.97151,17.91452 L -87.55905,978.04989 A 10.617073,10.617073 0 0 1 -95.18931,981.03569 Z"
|
||||
id="path3874"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<path
|
||||
style="fill:#c42e24;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 1213.1531,1680.9287 -669.2496,-58.25 -2.7504,-0.25 c -2.6328,-0.38 -5.0232,-1.07 -7.5,-1.75 -3.812,-1.16 -7.504,-2.58 -11,-4.5 -2.2928,-1.23 -4.6408,-2.68 -6.7496,-4.25 -1.3248,-0.97 -2.7664,-1.93 -4,-3 -1.0368,-0.94 -1.9816,-2.04 -3,-3 -0.8208,-0.91 -1.7088,-1.55 -2.5008,-2.5 -0.7536,-0.85 -1.332,-1.81 -2,-2.75 -0.676,-0.85 -1.3824,-1.59 -2,-2.5 -0.5536,-0.84 -0.976,-1.58 -1.4992,-2.5 l -1.5008,-2.5 c -0.5264,-0.83 -0.8504,-1.83 -1.2496,-2.75 -0.4488,-0.8 -0.8992,-1.59 -1.2496,-2.5 -0.3992,-0.91 -0.6792,-1.89 -1,-2.75 -0.3792,-1.04 -0.7304,-1.78 -1,-2.75 -0.2896,-0.98 -0.5432,-1.98 -0.7504,-3 -0.2848,-1.02 -0.5744,-2.15 -0.7504,-3.25 -0.2024,-1.16 -0.4056,-2.34 -0.4992,-3.5 -0.1848,-1.41 -0.2072,-2.94 -0.2504,-4.25 l 0,-1.25 -0.5,-168 35.5,-35.25 -36.2504,-3.25 1,-254.75 34.7504,-34.5 -34.7504,-3 -0.4992,-173 c 0.032,-1.328 0.1408,-2.774 0.2496,-4 0.8952,-11.69904 5.6408,-22.03496 13.2496,-29.5 l 106.5008,-105.25 c 9.3072,-9.17664 22.5168,-14.28736 37.2496,-13 l 669.2496,58.75 c 28.2904,2.492 51.1904,27.184 51.2504,55.25 l 0.2504,173.25 -34.5008,34.25 34.7504,3 -1,255 -35.2496,34.75 36,3.25 0.4992,168 c 0.021,14.4992 -6.0584,27.0636 -15.7496,35.5 -0.4256,0.3279 -0.828,0.6684 -1.2504,1 l -103,102.25 -2,1.75 c -1.0592,0.94 -2.1,2.03 -3.2496,2.75 -0.9896,0.77 -2.16,1.29 -3.2496,2 -0.9808,0.54 -1.9504,1.18 -3,1.75 -0.96,0.46 -2.0208,0.87 -3,1.25 -1.06,0.38 -1.9504,0.95 -3,1.25 -1.0104,0.33 -2.1904,0.5 -3.2504,0.75 -1.12,0.26 -2.1304,0.49 -3.2504,0.75 -1.1096,0.17 -2.1,0.39 -3.2496,0.5 -1.24,0.14 -2.5,0.25 -3.7504,0.25 -1.34,0.13 -2.84,0.13 -4.2496,0 L 1213.1531,1680.9287 z m 121.5,-106.25 c 0.4992,-0.1066 1.0176,-0.1362 1.5,-0.25 1.0496,-0.32 1.94,-0.6 3,-1 C 1337.6267,1573.9445 1336.2515,1574.3161 1334.6531,1574.6787 z m -700.5,-61 c -1.3416,-0.5561 -2.4592,-1.3436 -3.7504,-2 -1.528,-0.7774 -3.0504,-1.5904 -4.4992,-2.5 1.0256,0.6311 1.9552,1.4171 3,2 C 630.6275,1512.1137 632.3131,1512.9249 634.1531,1513.6787 z m -32.7504,-36.25 c -0.5016,-1.6366 -0.9056,-3.3138 -1.2496,-5 -0.344,-1.6862 -0.8216,-3.2736 -1,-5 0.1136,1.27 0.32,2.27 0.5,3.5 0.112,0.4814 0.3856,0.9701 0.5,1.5 0.128,0.593 0.098,1.2272 0.2496,1.75 0.2584,1.03 0.4416,1.98 0.7504,3 L 601.4027,1477.4287 z m 500.5008,-81 1,-237 34.7496,-34.5 -35,-3 0,-37 0,-0.5 0,-2 -0.2504,-2 -0.4992,-1.75 -0.2504,-1.5 -0.5,-1.5 -0.5,-1.5 -0.7496,-1.25 -0.7504,-1.5 -0.7504,-1.25 -0.7496,-1.25 -1,-1.5 -1.2504,-1.25 -1.2496,-1.5 -1.5,-1.25 -2,-1.75 c -1.12,-0.8 -2.52,-1.52 -3.7496,-2.25 -1.78,-0.98 -3.4904,-1.86 -5.5008,-2.5 -1.2792,-0.33 -2.6496,-0.55 -4,-0.75 l -1.4992,-0.25 -316.2504,-27.5 -1,241.25 -35.7496,35.25 36.4992,3.25 0,31.75 c 0,14.67 12.0552,27.69 26.7504,29 L 1101.9035,1396.4287 z m -327.7504,-478.5 1.5,-0.5 1.7496,-0.5 C 776.2851,917.21686 775.1979,917.49502 774.1531,917.9287 z m -175.7504,-127.75 c -0.01,-1.75728 0.082,-3.55328 0.2504,-5.25 -0.043,0.4272 -0.2176,0.81864 -0.2504,1.25 C 598.2979,787.5387 598.3877,788.7527 598.4027,790.1787 Z"
|
||||
id="path4611"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4613"
|
||||
style="fill:url(#linearGradient4705);fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 1209.1,979.273 -106.4,105.137 0.03,0.63 106.37,-105.212 0,-0.555 m -0.13,-2.152 -106.36,105.209 0.09,2.08 106.4,-105.137 -0.13,-2.152 m -0.31,-1.914 -106.34,105.193 0.29,1.93 106.36,-105.209 -0.31,-1.914 m -0.32,-1.641 -106.37,105.104 0.35,1.73 106.34,-105.193 -0.32,-1.641 m -0.41,-1.617 -106.32,105.191 0.36,1.53 106.37,-105.104 -0.41,-1.617 m -0.48,-1.48 -106.38,105.191 0.54,1.48 106.32,-105.191 -0.48,-1.48 m -0.61,-1.407 -106.3,105.208 0.53,1.39 106.38,-105.191 -0.61,-1.407 m -0.58,-1.355 -106.4,105.163 0.68,1.4 106.3,-105.208 -0.58,-1.355 m -0.69,-1.352 -106.38,105.155 0.67,1.36 106.4,-105.163 -0.69,-1.352 m -0.8,-1.343 -106.34,105.198 0.76,1.3 106.38,-105.155 -0.8,-1.343 m -0.83,-1.344 -106.38,105.212 0.87,1.33 106.34,-105.198 -0.83,-1.344 m -0.99,-1.309 -106.38,105.161 0.99,1.36 106.38,-105.212 -0.99,-1.309 m -1.08,-1.355 -106.41,105.126 1.11,1.39 106.38,-105.161 -1.08,-1.355 m -1.34,-1.418 -106.38,105.144 1.31,1.4 106.41,-105.126 -1.34,-1.418 m -1.6,-1.555 -106.34,105.279 1.56,1.42 106.38,-105.144 -1.6,-1.555 m -2.05,-1.609 -106.34,105.168 2.05,1.72 106.34,-105.279 -2.05,-1.609 m -3.5,-2.184 -106.36,105.102 c 1.23,0.73 2.4,1.45 3.52,2.25 l 106.34,-105.168 c -1.08,-0.813 -2.29,-1.563 -3.5,-2.184 m -5.69,-2.347 -106.4,105.109 c 2.01,0.64 3.95,1.36 5.73,2.34 l 106.36,-105.102 c -1.79,-0.992 -3.74,-1.836 -5.69,-2.347 m -4.03,-0.895 -106.34,105.174 c 1.35,0.2 2.69,0.5 3.97,0.83 l 106.4,-105.109 c -1.31,-0.43 -2.65,-0.7 -4.03,-0.895 m -1.39,-0.176 -106.32,105.21 1.37,0.14 106.34,-105.174 -1.39,-0.176"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4615"
|
||||
style="fill:#871f1c;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 1209.16,1016.87 -106.4,105.16 160.86,14.14 106.34,-105.24 -160.8,-14.06"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4617"
|
||||
style="fill:#871f1c;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 1209.16,1016.87 -106.4,105.16 160.86,14.14 106.34,-105.24 -160.8,-14.06"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4619"
|
||||
style="fill:#871f1c;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 1209.1,979.828 -106.37,105.212 0.03,36.99 106.4,-105.16 -0.06,-37.042"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4621"
|
||||
style="fill:#c42e24;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="M 786.051,916.102 679.719,1021.34 1075.95,1056.03 1182.27,950.82 786.051,916.102"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4623"
|
||||
style="fill:url(#linearGradient4707);fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 784.848,916.051 -106.391,105.129 1.262,0.16 106.332,-105.238 -1.203,-0.051 m -2.153,0 -106.343,105.199 2.105,-0.07 106.391,-105.129 -2.153,0 m -1.914,0.176 -106.363,105.153 1.934,-0.13 106.343,-105.199 -1.914,0.176 m -1.777,0.253 -106.336,105.16 1.75,-0.26 106.363,-105.153 -1.777,0.253 m -1.68,0.336 -106.347,105.164 1.691,-0.34 106.336,-105.16 -1.68,0.336 m -1.656,0.496 -106.305,105.178 1.614,-0.51 106.347,-105.164 -1.656,0.496 m -1.609,0.626 -106.325,105.122 1.629,-0.57 106.305,-105.178 -1.609,0.626 m -1.532,0.73 -106.355,105.172 1.562,-0.78 106.325,-105.122 -1.532,0.73 m -1.597,0.863 -106.356,105.209 1.598,-0.9 106.355,-105.172 -1.597,0.863 m -1.598,1.008 -106.398,105.191 1.64,-0.99 106.356,-105.209 -1.598,1.008 m -1.703,1.32 -106.34,105.321 1.645,-1.45 106.398,-105.191 -1.703,1.32 m -1.07,1.043 -106.368,105.238 1.098,-0.96 106.34,-105.321 -1.07,1.043"
|
||||
inkscape:connector-curvature="0" />
|
||||
<g
|
||||
id="g4625">
|
||||
<g
|
||||
clip-path="url(#clipPath3195)"
|
||||
id="g4627">
|
||||
<path
|
||||
id="path4629"
|
||||
style="fill:#7f1d1c;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 611.836,756.738 -106.34,105.207 c -7.609,7.465 -12.527,17.739 -13.422,29.438 L 598.445,786.152 c 0.879,-11.687 5.825,-21.863 13.391,-29.414"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4631"
|
||||
style="fill:#841f1c;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="M 598.445,786.152 492.074,891.383 c -0.109,1.226 -0.144,2.613 -0.176,3.941 L 598.301,790.207 c -0.016,-1.426 0.039,-2.695 0.144,-4.055"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
<path
|
||||
id="path4633"
|
||||
style="fill:#d93023;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 1369.96,1030.93 -0.29,-173.184 c -0.06,-28.066 -22.94,-52.91 -51.23,-55.402 L 649.238,743.691 c -28.164,-2.461 -51.05,18.399 -50.937,46.516 l 0.289,173.18 160.937,14.133 -0.086,-37.102 c 0,-14.715 11.875,-25.594 26.61,-24.316 l 396.219,34.718 c 14.74,1.328 26.74,14.25 26.83,29.008 l 0.06,37.042 160.8,14.06"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4635"
|
||||
style="fill:#871f1c;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="M 598.59,963.387 492.281,1068.54 653.164,1082.71 759.527,977.52 598.59,963.387"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4637"
|
||||
style="fill:#871f1c;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="M 598.59,963.387 492.281,1068.54 653.164,1082.71 759.527,977.52 598.59,963.387"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4639"
|
||||
style="fill:#871f1c;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="M 598.301,790.207 491.898,895.324 492.281,1068.54 598.59,963.387 598.301,790.207"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4641"
|
||||
style="fill:#e63f46;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 1369.03,1323.22 1.02,-254.93 -160.81,-14.08 -1.03,254.89 160.82,14.12"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4643"
|
||||
style="fill:#e23535;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 1369.03,1323.22 1.02,-254.93 -160.81,-14.08 -1.03,254.89 160.82,14.12"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4645"
|
||||
style="fill:#871f1c;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 1209.24,1054.21 -106.32,105.18 -1.03,254.93 106.32,-105.22 1.03,-254.89"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4647"
|
||||
style="fill:#871f1c;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 1208.21,1309.1 -106.32,105.22 160.81,14.04 106.33,-105.14 -160.82,-14.12"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4649"
|
||||
style="fill:#871f1c;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 1208.21,1309.1 -106.32,105.22 160.81,14.04 106.33,-105.14 -160.82,-14.12"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4651"
|
||||
style="fill:#e63f46;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 758.598,1269.75 0.972,-254.95 -160.871,-14.08 -1.019,254.94 160.918,14.09"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4653"
|
||||
style="fill:#e23535;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 758.598,1269.75 0.972,-254.95 -160.871,-14.08 -1.019,254.94 160.918,14.09"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4655"
|
||||
style="fill:#871f1c;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 598.699,1000.72 -106.39,105.23 -0.965,254.83 106.336,-105.12 1.019,-254.94"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4657"
|
||||
style="fill:#871f1c;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 597.68,1255.66 -106.336,105.12 160.879,14.11 106.375,-105.14 -160.918,-14.09"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4659"
|
||||
style="fill:#871f1c;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 1209.04,1347.16 -106.36,105.1 0.05,32.04 106.37,-105.21 -0.06,-31.93"
|
||||
inkscape:connector-curvature="0" />
|
||||
<g
|
||||
id="g4661">
|
||||
<g
|
||||
clip-path="url(#clipPath3235)"
|
||||
id="g4663">
|
||||
<path
|
||||
id="path4665"
|
||||
style="fill:#841f1c;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 1209.1,1379.09 -106.37,105.21 -0.12,2.07 106.4,-105.21 0.09,-2.07"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4667"
|
||||
style="fill:#7f1d1c;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 1209.01,1381.16 -106.4,105.21 c -0.42,6.18 -3,11.54 -6.97,15.44 l 106.35,-105.17 c 3.96,-3.97 6.54,-9.32 7.02,-15.48"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
<path
|
||||
id="path4669"
|
||||
style="fill:url(#linearGradient4709);fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 1354.74,1564.66 -106.38,105.11 2.07,-1.95 106.35,-105.11 -2.04,1.95 m -3.29,2.54 -106.34,105.12 c 1.15,-0.72 2.19,-1.61 3.25,-2.55 l 106.38,-105.11 c -1.09,0.84 -2.14,1.7 -3.29,2.54 m -3.14,2.04 -106.34,105.21 c 1.09,-0.71 2.15,-1.36 3.14,-2.13 l 106.34,-105.12 c -0.99,0.7 -2.05,1.4 -3.14,2.04 m -3.03,1.64 -106.33,105.24 c 1.05,-0.57 2.04,-1.13 3.02,-1.67 l 106.34,-105.21 c -0.99,0.58 -1.97,1.18 -3.03,1.64 m -3.02,1.46 -106.31,105.15 c 0.98,-0.38 2.04,-0.91 3,-1.37 l 106.33,-105.24 c -0.98,0.56 -1.94,1.02 -3.02,1.46 m -3.03,1.11 -106.38,105.14 c 1.05,-0.3 2.04,-0.72 3.1,-1.1 l 106.31,-105.15 c -1,0.38 -2.03,0.78 -3.03,1.11 m -3.13,0.96 -106.35,105.14 c 1.06,-0.25 2.09,-0.63 3.1,-0.96 l 106.38,-105.14 c -1.06,0.4 -2.08,0.64 -3.13,0.96 m -3.29,0.66 -106.32,105.22 c 1.12,-0.26 2.14,-0.48 3.26,-0.74 l 106.35,-105.14 c -1.06,0.25 -2.17,0.51 -3.29,0.66 m -3.39,0.52 -106.37,105.18 c 1.15,-0.11 2.33,-0.31 3.44,-0.48 l 106.32,-105.22 c -1.08,0.23 -2.23,0.39 -3.39,0.52 m -3.76,0.3 -106.31,105.13 c 1.25,0 2.46,-0.11 3.7,-0.25 l 106.37,-105.18 c -1.25,0.22 -2.49,0.26 -3.76,0.3 m -4.06,-0.04 -106.37,105.17 c 1.41,0.13 2.78,0.13 4.12,0 l 106.31,-105.13 c -1.3,0.05 -2.72,0.05 -4.06,-0.04 m -2.2,-0.04 -106.36,105.08 2.19,0.13 106.37,-105.17 -2.2,-0.04"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4671"
|
||||
style="fill:#871f1c;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 598.508,1294.05 -106.383,105.16 0.512,168.09 106.34,-105.14 -0.469,-168.11"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4673"
|
||||
style="fill:#c42e24;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 650.168,1517.51 -106.344,105.2 669.216,58.18 106.36,-105.08 -669.232,-58.3"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4675"
|
||||
style="fill:#e63f46;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 650.168,1517.51 669.232,58.3 c 28.22,2.28 51.01,-18.45 50.97,-46.64 l -0.58,-168.06 -160.75,-13.95 0.06,31.93 c 0,14.71 -11.86,25.58 -26.57,24.3 l -396.296,-34.43 c -14.695,-1.31 -26.757,-14.28 -26.757,-28.95 l -0.036,-31.93 -160.933,-14.03 0.469,168.11 c 0.062,28.12 23.019,52.85 51.191,55.35"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
id="path4677"
|
||||
style="fill:#e63f46;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 650.168,1517.51 669.232,58.3 c 28.22,2.28 51.01,-18.45 50.97,-46.64 l -0.58,-168.06 -160.75,-13.95 0.06,31.93 c 0,14.71 -11.86,25.58 -26.57,24.3 l -396.296,-34.43 c -14.695,-1.31 -26.757,-14.28 -26.757,-28.95 l -0.036,-31.93 -160.933,-14.03 0.469,168.11 c 0.062,28.12 23.019,52.85 51.191,55.35"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:url(#linearGradient4711);fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 543.9031,1622.6787 -2.75,-0.25 c -2.63304,-0.38 -5.02304,-1.07 -7.5,-1.75 -3.812,-1.16 -7.504,-2.58 -11,-4.5 -2.29304,-1.23 -4.64096,-2.68 -6.75,-4.25 -1.324,-0.97 -2.766,-1.93 -4,-3 -1.036,-0.94 -1.98096,-2.04 -3,-3 -0.82,-0.91 -1.708,-1.55 -2.5,-2.5 -0.754,-0.85 -1.332,-1.81 -2,-2.75 -0.676,-0.85 -1.38304,-1.59 -2,-2.5 -0.554,-0.84 -0.976,-1.58 -1.5,-2.5 l -1.5,-2.5 c -0.52704,-0.83 -0.85096,-1.83 -1.25,-2.75 -0.44904,-0.8 -0.89896,-1.59 -1.25,-2.5 -0.39904,-0.91 -0.67904,-1.89 -1,-2.75 -0.37896,-1.04 -0.73,-1.78 -1,-2.75 -0.28904,-0.98 -0.54304,-1.98 -0.75,-3 -0.28504,-1.02 -0.574,-2.15 -0.75,-3.25 -0.20304,-1.16 -0.406,-2.34 -0.5,-3.5 -0.184,-1.41 -0.20696,-2.94 -0.25,-4.25 l 0,-1.25 106.25,-105 0,1 c 0.031,1.37 0.075,2.94 0.25,4.25 0.114,1.27 0.32,2.27 0.5,3.5 0.238,1.02 0.46096,2.26 0.75,3.25 0.258,1.03 0.44096,1.98 0.75,3 l 1,2.75 1,2.75 c 0.332,1 0.848,1.94 1.25,2.75 l 1.25,2.5 1.5,2.5 1.75,2.5 1.75,2.5 c 0.692,0.95 1.51904,1.92 2.25,2.75 0.80096,1.02 1.64504,1.9 2.5,2.75 0.946,0.99 1.96104,1.84 3,2.75 1.28104,1.1 2.37104,2.28 3.75,3.25 2.13696,1.57 4.438,2.96 6.75,4.25 3.44896,1.87 7.172,3.4 11,4.5 2.47296,0.71 5.144,1.18 7.75,1.5 l 2.5,0.25 L 543.9031,1622.6787 Z"
|
||||
id="path4679"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;filter:url(#filter4595)"
|
||||
d="m 1086.5,734.90625 -0.031,0.25 c -0.012,0.11391 -0.04,0.23256 -0.063,0.34375 -0.034,0.16683 -0.067,0.341 -0.125,0.5 -0.018,0.0517 -0.042,0.10554 -0.062,0.15625 l -0.094,0.1875 c -0.057,0.11744 -0.119,0.23297 -0.1875,0.34375 -0.012,0.0198 -0.019,0.043 -0.031,0.0625 l -0.062,0.0937 -0.125,0.15625 -0.125,0.1875 c -0.029,0.0332 -0.064,0.0616 -0.094,0.0937 l -0.062,0.0625 c -0.1348,0.13899 -0.2831,0.25777 -0.4375,0.375 l -0.031,0.0312 c -0.168,0.12447 -0.3444,0.21734 -0.5313,0.3125 -0.2,0.1018 -0.4076,0.18634 -0.625,0.25 -0.2174,0.0637 -0.4572,0.10425 -0.6875,0.125 l -49.5,4.34375 c -0.2198,0.0191 -0.4487,0.0224 -0.6562,0 l -0.25,-0.0312 -0.094,-0.0312 c -0.032,-0.007 -0.062,-0.0231 -0.094,-0.0312 l -0.2187,-0.0625 -0.1875,-0.0625 -0.2188,-0.0937 -0.1562,-0.0937 c -0.01,-0.004 -0.024,0.004 -0.031,0 l -0.1876,-0.125 -0.1562,-0.125 c -0.023,-0.0185 -0.041,-0.0433 -0.062,-0.0625 l -0.062,-0.0312 c -0.027,-0.0248 -0.036,-0.068 -0.062,-0.0937 l -0.125,-0.125 c -0.4826,-0.53581 -0.7813,-1.2496 -0.7813,-2.0625 l 0,2 c 0,0.8129 0.2987,1.52669 0.7813,2.0625 l 0.125,0.125 c 0.026,0.0258 0.035,0.069 0.062,0.0937 l 0.062,0.0312 c 0.022,0.0192 0.04,0.044 0.062,0.0625 l 0.1562,0.125 0.1876,0.125 c 0.01,0.004 0.024,-0.004 0.031,0 l 0.1562,0.0937 0.2188,0.0937 0.1875,0.0625 0.2187,0.0625 c 0.032,0.008 0.062,0.0241 0.094,0.0312 l 0.094,0.0312 0.25,0.0312 c 0.2075,0.0224 0.4364,0.0191 0.6562,0 l 49.5,-4.34375 c 0.2303,-0.0208 0.4701,-0.0613 0.6875,-0.125 0.2174,-0.0637 0.425,-0.1482 0.625,-0.25 0.1869,-0.0952 0.3632,-0.18803 0.5313,-0.3125 l 0.031,-0.0312 c 0.1544,-0.11723 0.3027,-0.23601 0.4375,-0.375 l 0.062,-0.0625 c 0.03,-0.0322 0.065,-0.0606 0.094,-0.0937 l 0.125,-0.1875 0.125,-0.15625 0.062,-0.0937 c 0.013,-0.0195 0.019,-0.0427 0.031,-0.0625 0.069,-0.11078 0.1309,-0.22631 0.1875,-0.34375 l 0.094,-0.1875 c 0.021,-0.0507 0.044,-0.10458 0.062,-0.15625 0.058,-0.159 0.091,-0.33318 0.125,-0.5 0.023,-0.11119 0.05,-0.22984 0.063,-0.34375 l 0.031,-0.25 L 1086.5,736.81255 Z"
|
||||
transform="matrix(8,0,0,-8,-7482.8469,6874.4287)"
|
||||
id="path4703"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccccccccccsccccccccccccccccccscccccccccccccccccscccccccccccccccccc"
|
||||
clip-path="url(#clipPath4591)" />
|
||||
<rect
|
||||
transform="scale(1,-1)"
|
||||
y="-1725.8152"
|
||||
x="357.98178"
|
||||
height="1019.137"
|
||||
width="1019.137"
|
||||
id="rect3585-3"
|
||||
style="opacity:0.8;color:#000000;fill:none;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer3"
|
||||
inkscape:label="PLACE YOUR PICTOGRAM HERE"
|
||||
style="display:inline" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="BADGE"
|
||||
style="display:none"
|
||||
sodipodi:insensitive="true">
|
||||
<g
|
||||
style="display:inline"
|
||||
transform="translate(-340.00001,-581)"
|
||||
id="g4394"
|
||||
clip-path="none">
|
||||
<g
|
||||
id="g855">
|
||||
<g
|
||||
inkscape:groupmode="maskhelper"
|
||||
id="g870"
|
||||
clip-path="url(#clipPath873)"
|
||||
style="opacity:0.6;filter:url(#filter891)">
|
||||
<path
|
||||
transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-237.54282)"
|
||||
d="m 264,552.36218 c 0,6.62742 -5.37258,12 -12,12 -6.62742,0 -12,-5.37258 -12,-12 0,-6.62741 5.37258,-12 12,-12 C 258.62742,540.36218 264,545.73477 264,552.36218 Z"
|
||||
sodipodi:ry="12"
|
||||
sodipodi:rx="12"
|
||||
sodipodi:cy="552.36218"
|
||||
sodipodi:cx="252"
|
||||
id="path844"
|
||||
style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
sodipodi:type="arc" />
|
||||
</g>
|
||||
<g
|
||||
id="g862">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="color:#000000;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="path4398"
|
||||
sodipodi:cx="252"
|
||||
sodipodi:cy="552.36218"
|
||||
sodipodi:rx="12"
|
||||
sodipodi:ry="12"
|
||||
d="m 264,552.36218 c 0,6.62742 -5.37258,12 -12,12 -6.62742,0 -12,-5.37258 -12,-12 0,-6.62741 5.37258,-12 12,-12 C 258.62742,540.36218 264,545.73477 264,552.36218 Z"
|
||||
transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-238.54282)" />
|
||||
<path
|
||||
transform="matrix(1.25,0,0,1.25,33,-100.45273)"
|
||||
d="m 264,552.36218 c 0,6.62742 -5.37258,12 -12,12 -6.62742,0 -12,-5.37258 -12,-12 0,-6.62741 5.37258,-12 12,-12 C 258.62742,540.36218 264,545.73477 264,552.36218 Z"
|
||||
sodipodi:ry="12"
|
||||
sodipodi:rx="12"
|
||||
sodipodi:cy="552.36218"
|
||||
sodipodi:cx="252"
|
||||
id="path4400"
|
||||
style="color:#000000;fill:#dd4814;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
sodipodi:type="star"
|
||||
style="color:#000000;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="path4459"
|
||||
sodipodi:sides="5"
|
||||
sodipodi:cx="666.19574"
|
||||
sodipodi:cy="589.50385"
|
||||
sodipodi:r1="7.2431178"
|
||||
sodipodi:r2="4.3458705"
|
||||
sodipodi:arg1="1.0471976"
|
||||
sodipodi:arg2="1.6755161"
|
||||
inkscape:flatsided="false"
|
||||
inkscape:rounded="0.1"
|
||||
inkscape:randomized="0"
|
||||
d="m 669.8173,595.77657 c -0.39132,0.22593 -3.62645,-1.90343 -4.07583,-1.95066 -0.44938,-0.0472 -4.05653,1.36297 -4.39232,1.06062 -0.3358,-0.30235 0.68963,-4.03715 0.59569,-4.47913 -0.0939,-0.44198 -2.5498,-3.43681 -2.36602,-3.8496 0.18379,-0.41279 4.05267,-0.59166 4.44398,-0.81759 0.39132,-0.22593 2.48067,-3.48704 2.93005,-3.4398 0.44938,0.0472 1.81505,3.67147 2.15084,3.97382 0.3358,0.30236 4.08294,1.2817 4.17689,1.72369 0.0939,0.44198 -2.9309,2.86076 -3.11469,3.27355 C 669.9821,591.68426 670.20862,595.55064 669.8173,595.77657 Z"
|
||||
transform="matrix(1.511423,-0.16366377,0.16366377,1.511423,-755.37346,-191.93651)" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 42 KiB |
Loading…
Reference in New Issue