astara/akanda/rug/api/nova.py

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