233 lines
6.4 KiB
Python
Executable File
233 lines
6.4 KiB
Python
Executable File
# Copyright 2014 DreamHost, LLC
|
|
#
|
|
# Author: DreamHost, LLC
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
from datetime import datetime
|
|
|
|
from novaclient.v1_1 import client
|
|
from novaclient import exceptions as novaclient_exceptions
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
OPTIONS = [
|
|
cfg.StrOpt(
|
|
'router_ssh_public_key',
|
|
help="Path to the SSH public key for the 'akanda' user within "
|
|
"router appliance instances",
|
|
default='/etc/akanda-rug/akanda.pub')
|
|
]
|
|
cfg.CONF.register_opts(OPTIONS)
|
|
|
|
|
|
class InstanceInfo(object):
|
|
def __init__(self, instance_id, name, management_port=None, ports=(),
|
|
image_uuid=None, booting=False, last_boot=None):
|
|
self.id_ = instance_id
|
|
self.name = name
|
|
self.image_uuid = image_uuid
|
|
self.booting = booting
|
|
self.last_boot = datetime.utcnow() if booting else last_boot
|
|
|
|
self.instance_up = True
|
|
self.boot_duration = None
|
|
self.nova_status = None
|
|
|
|
self.management_port = management_port
|
|
self._ports = ports
|
|
|
|
@property
|
|
def management_address(self):
|
|
return str(self.management_port.fixed_ips[0].ip_address)
|
|
|
|
@property
|
|
def time_since_boot(self):
|
|
if self.last_boot:
|
|
return datetime.utcnow() - self.last_boot
|
|
|
|
@property
|
|
def ports(self):
|
|
return self._ports
|
|
|
|
@ports.setter
|
|
def ports(self, port_list):
|
|
self._ports = [p for p in port_list if p != self.management_port]
|
|
|
|
def confirm_up(self):
|
|
if self.booting:
|
|
self.booting = False
|
|
if self.last_boot:
|
|
self.boot_duration = (datetime.utcnow() - self.last_boot)
|
|
|
|
|
|
class Nova(object):
|
|
def __init__(self, conf):
|
|
self.conf = conf
|
|
self.client = client.Client(
|
|
conf.admin_user,
|
|
conf.admin_password,
|
|
conf.admin_tenant_name,
|
|
auth_url=conf.auth_url,
|
|
auth_system=conf.auth_strategy,
|
|
region_name=conf.auth_region)
|
|
|
|
def create_instance(self, router_id, image_uuid, make_ports_callback):
|
|
mgt_port, instance_ports = make_ports_callback()
|
|
|
|
nics = [{'net-id': p.network_id, 'v4-fixed-ip': '', 'port-id': p.id}
|
|
for p in ([mgt_port] + instance_ports)]
|
|
|
|
LOG.debug('creating instance for router %s with image %s',
|
|
router_id, image_uuid)
|
|
name = 'ak-' + router_id
|
|
|
|
server = self.client.servers.create(
|
|
name,
|
|
image=image_uuid,
|
|
flavor=self.conf.router_instance_flavor,
|
|
nics=nics,
|
|
config_drive=True,
|
|
userdata=_format_userdata(mgt_port)
|
|
)
|
|
|
|
instance_info = InstanceInfo(
|
|
server.id,
|
|
name,
|
|
mgt_port,
|
|
instance_ports,
|
|
image_uuid,
|
|
True
|
|
)
|
|
|
|
assert server and server.created
|
|
|
|
instance_info.nova_status = server.status
|
|
return instance_info
|
|
|
|
def get_instance_info_for_obj(self, router_id):
|
|
instance = self.get_instance_for_obj(router_id)
|
|
|
|
if instance:
|
|
return InstanceInfo(
|
|
instance.id,
|
|
instance.name,
|
|
image_uuid=instance.image['id']
|
|
)
|
|
|
|
def get_instance_for_obj(self, router_id):
|
|
instances = self.client.servers.list(
|
|
search_opts=dict(name='ak-' + router_id)
|
|
)
|
|
|
|
if instances:
|
|
return instances[0]
|
|
else:
|
|
return None
|
|
|
|
def get_instance_by_id(self, instance_id):
|
|
try:
|
|
return self.client.servers.get(instance_id)
|
|
except novaclient_exceptions.NotFound:
|
|
return None
|
|
|
|
def destroy_instance(self, instance_info):
|
|
if instance_info:
|
|
LOG.debug('deleting instance for router %s', instance_info.name)
|
|
self.client.servers.delete(instance_info.id_)
|
|
|
|
def boot_instance(self, prev_instance_info, router_id, router_image_uuid,
|
|
make_ports_callback):
|
|
|
|
instance_info = None
|
|
if not prev_instance_info:
|
|
instance = self.get_instance_for_obj(router_id)
|
|
else:
|
|
instance = self.get_instance_by_id(prev_instance_info.id_)
|
|
|
|
# check to make sure this instance isn't pre-existing
|
|
if instance:
|
|
if 'BUILD' in instance.status:
|
|
# return the same instance with updated status
|
|
prev_instance_info.nova_status = instance.status
|
|
return prev_instance_info
|
|
|
|
self.client.servers.delete(instance_info.id_)
|
|
return None
|
|
|
|
# it is now safe to attempt boot
|
|
instance_info = self.create_instance(
|
|
router_id,
|
|
router_image_uuid,
|
|
make_ports_callback
|
|
)
|
|
return instance_info
|
|
|
|
# TODO(mark): Convert this to dynamic yaml, proper network prefix and ssh-keys
|
|
|
|
TEMPLATE = """#cloud-config
|
|
|
|
cloud_config_modules:
|
|
- emit_upstart
|
|
- set_hostname
|
|
- locale
|
|
- set-passwords
|
|
- timezone
|
|
- disable-ec2-metadata
|
|
- runcmd
|
|
|
|
output: {all: '| tee -a /var/log/cloud-init-output.log'}
|
|
|
|
debug:
|
|
- verbose: true
|
|
|
|
bootcmd:
|
|
- /usr/local/bin/akanda-configure-management %(mac_address)s %(ip_address)s/64
|
|
|
|
users:
|
|
- name: akanda
|
|
gecos: Akanda
|
|
groups: users
|
|
shell: /bin/bash
|
|
sudo: ALL=(ALL) NOPASSWD:ALL
|
|
lock-passwd: true
|
|
ssh-authorized-keys:
|
|
- %(ssh_public_key)s
|
|
|
|
final_message: "Akanda appliance is running"
|
|
""" # noqa
|
|
|
|
|
|
def _router_ssh_key():
|
|
key = cfg.CONF.router_ssh_public_key
|
|
if not key:
|
|
return ''
|
|
try:
|
|
with open(key) as out:
|
|
return out.read()
|
|
except IOError:
|
|
LOG.warning('Could not load router ssh public key from %s' % key)
|
|
return ''
|
|
|
|
|
|
def _format_userdata(mgt_port):
|
|
ctxt = {
|
|
'ssh_public_key': _router_ssh_key(),
|
|
'mac_address': mgt_port.mac_address,
|
|
'ip_address': mgt_port.fixed_ips[0].ip_address,
|
|
}
|
|
return TEMPLATE % ctxt
|