280 lines
10 KiB
Python
280 lines
10 KiB
Python
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import base64
|
|
|
|
import jinja2
|
|
from oslo_log import log as logging
|
|
from oslo_utils import encodeutils
|
|
|
|
from kube import base
|
|
from senlin.common import consts
|
|
from senlin.common import exception as exc
|
|
from senlin.common.i18n import _
|
|
from senlin.common import schema
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class ServerProfile(base.KubeBaseProfile):
|
|
"""Profile for an kubernetes master server."""
|
|
|
|
VERSIONS = {
|
|
'1.0': [
|
|
{'status': consts.EXPERIMENTAL, 'since': '2017.10'}
|
|
]
|
|
}
|
|
|
|
KEYS = (
|
|
CONTEXT, FLAVOR, IMAGE, KEY_NAME,
|
|
PUBLIC_NETWORK, BLOCK_DEVICE_MAPPING_V2,
|
|
) = (
|
|
'context', 'flavor', 'image', 'key_name',
|
|
'public_network', 'block_device_mapping_v2',
|
|
)
|
|
|
|
INTERNAL_KEYS = (
|
|
KUBEADM_TOKEN, KUBE_MASTER_IP, SECURITY_GROUP,
|
|
PRIVATE_NETWORK, PRIVATE_SUBNET, PRIVATE_ROUTER,
|
|
KUBE_MASTER_FLOATINGIP, KUBE_MASTER_FLOATINGIP_ID,
|
|
SCALE_OUT_RECV_ID, SCALE_OUT_URL,
|
|
) = (
|
|
'kubeadm_token', 'kube_master_ip', 'security_group',
|
|
'private_network', 'private_subnet', 'private_router',
|
|
'kube_master_floatingip', 'kube_master_floatingip_id',
|
|
'scale_out_recv_id', 'scale_out_url',
|
|
)
|
|
|
|
NETWORK_KEYS = (
|
|
PORT, FIXED_IP, NETWORK, PORT_SECURITY_GROUPS,
|
|
FLOATING_NETWORK, FLOATING_IP,
|
|
) = (
|
|
'port', 'fixed_ip', 'network', 'security_groups',
|
|
'floating_network', 'floating_ip',
|
|
)
|
|
|
|
BDM2_KEYS = (
|
|
BDM2_UUID, BDM2_SOURCE_TYPE, BDM2_DESTINATION_TYPE,
|
|
BDM2_DISK_BUS, BDM2_DEVICE_NAME, BDM2_VOLUME_SIZE,
|
|
BDM2_GUEST_FORMAT, BDM2_BOOT_INDEX, BDM2_DEVICE_TYPE,
|
|
BDM2_DELETE_ON_TERMINATION,
|
|
) = (
|
|
'uuid', 'source_type', 'destination_type', 'disk_bus',
|
|
'device_name', 'volume_size', 'guest_format', 'boot_index',
|
|
'device_type', 'delete_on_termination',
|
|
)
|
|
|
|
properties_schema = {
|
|
CONTEXT: schema.Map(
|
|
_('Customized security context for operating servers.'),
|
|
),
|
|
FLAVOR: schema.String(
|
|
_('ID of flavor used for the server.'),
|
|
required=True,
|
|
updatable=True,
|
|
),
|
|
IMAGE: schema.String(
|
|
# IMAGE is not required, because there could be BDM or BDMv2
|
|
# support and the corresponding settings effective
|
|
_('ID of image to be used for the new server.'),
|
|
updatable=True,
|
|
),
|
|
KEY_NAME: schema.String(
|
|
_('Name of Nova keypair to be injected to server.'),
|
|
),
|
|
PUBLIC_NETWORK: schema.String(
|
|
_('Public network for kubernetes.'),
|
|
required=True,
|
|
),
|
|
BLOCK_DEVICE_MAPPING_V2: schema.List(
|
|
_('A list specifying the properties of block devices to be used '
|
|
'for this server.'),
|
|
schema=schema.Map(
|
|
_('A map specifying the properties of a block device to be '
|
|
'used by the server.'),
|
|
schema={
|
|
BDM2_UUID: schema.String(
|
|
_('ID of the source image, snapshot or volume'),
|
|
),
|
|
BDM2_SOURCE_TYPE: schema.String(
|
|
_("Volume source type, must be one of 'image', "
|
|
"'snapshot', 'volume' or 'blank'"),
|
|
required=True,
|
|
),
|
|
BDM2_DESTINATION_TYPE: schema.String(
|
|
_("Volume destination type, must be 'volume' or "
|
|
"'local'"),
|
|
required=True,
|
|
),
|
|
BDM2_DISK_BUS: schema.String(
|
|
_('Bus of the device.'),
|
|
),
|
|
BDM2_DEVICE_NAME: schema.String(
|
|
_('Name of the device(e.g. vda, xda, ....).'),
|
|
),
|
|
BDM2_VOLUME_SIZE: schema.Integer(
|
|
_('Size of the block device in MB(for swap) and '
|
|
'in GB(for other formats)'),
|
|
required=True,
|
|
),
|
|
BDM2_GUEST_FORMAT: schema.String(
|
|
_('Specifies the disk file system format(e.g. swap, '
|
|
'ephemeral, ...).'),
|
|
),
|
|
BDM2_BOOT_INDEX: schema.Integer(
|
|
_('Define the boot order of the device'),
|
|
),
|
|
BDM2_DEVICE_TYPE: schema.String(
|
|
_('Type of the device(e.g. disk, cdrom, ...).'),
|
|
),
|
|
BDM2_DELETE_ON_TERMINATION: schema.Boolean(
|
|
_('Whether to delete the volume when the server '
|
|
'stops.'),
|
|
),
|
|
}
|
|
),
|
|
),
|
|
}
|
|
|
|
def __init__(self, type_name, name, **kwargs):
|
|
super(ServerProfile, self).__init__(type_name, name, **kwargs)
|
|
self.server_id = None
|
|
|
|
def do_cluster_create(self, obj):
|
|
self._generate_kubeadm_token(obj)
|
|
self._create_security_group(obj)
|
|
self._create_network(obj)
|
|
|
|
def do_cluster_delete(self, obj):
|
|
if obj.dependents and 'kube-node' in obj.dependents:
|
|
msg = ("Cluster %s delete failed, "
|
|
"Node clusters %s must be deleted first." %
|
|
(obj.id, obj.dependents['kube-node']))
|
|
raise exc.EResourceDeletion(type='kubernetes.master',
|
|
id=obj.id,
|
|
message=msg)
|
|
self._delete_network(obj)
|
|
self._delete_security_group(obj)
|
|
|
|
def do_create(self, obj):
|
|
"""Create a server for the node object.
|
|
|
|
:param obj: The node object for which a server will be created.
|
|
"""
|
|
kwargs = {}
|
|
for key in self.KEYS:
|
|
if self.properties[key] is not None:
|
|
kwargs[key] = self.properties[key]
|
|
|
|
image_ident = self.properties[self.IMAGE]
|
|
if image_ident is not None:
|
|
image = self._validate_image(obj, image_ident, 'create')
|
|
kwargs.pop(self.IMAGE)
|
|
kwargs['imageRef'] = image.id
|
|
|
|
flavor_ident = self.properties[self.FLAVOR]
|
|
flavor = self._validate_flavor(obj, flavor_ident, 'create')
|
|
kwargs.pop(self.FLAVOR)
|
|
kwargs['flavorRef'] = flavor.id
|
|
|
|
keypair_name = self.properties[self.KEY_NAME]
|
|
if keypair_name:
|
|
keypair = self._validate_keypair(obj, keypair_name, 'create')
|
|
kwargs['key_name'] = keypair.name
|
|
|
|
kwargs['name'] = obj.name
|
|
|
|
metadata = self._build_metadata(obj, {})
|
|
kwargs['metadata'] = metadata
|
|
|
|
jj_vars = {}
|
|
cluster_data = self._get_cluster_data(obj)
|
|
kwargs['networks'] = [{'uuid': cluster_data[self.PRIVATE_NETWORK]}]
|
|
|
|
# Get user_data parameters from metadata
|
|
jj_vars['KUBETOKEN'] = cluster_data[self.KUBEADM_TOKEN]
|
|
jj_vars['MASTER_FLOATINGIP'] = cluster_data[
|
|
self.KUBE_MASTER_FLOATINGIP]
|
|
|
|
block_device_mapping_v2 = self.properties[self.BLOCK_DEVICE_MAPPING_V2]
|
|
if block_device_mapping_v2 is not None:
|
|
kwargs['block_device_mapping_v2'] = self._resolve_bdm(
|
|
obj, block_device_mapping_v2, 'create')
|
|
|
|
# user_data = self.properties[self.USER_DATA]
|
|
user_data = base.loadScript('./scripts/master.sh')
|
|
if user_data is not None:
|
|
# Use jinja2 to replace variables defined in user_data
|
|
try:
|
|
jj_t = jinja2.Template(user_data)
|
|
user_data = jj_t.render(**jj_vars)
|
|
except (jinja2.exceptions.UndefinedError, ValueError) as ex:
|
|
# TODO(anyone) Handle jinja2 error
|
|
pass
|
|
ud = encodeutils.safe_encode(user_data)
|
|
kwargs['user_data'] = encodeutils.safe_decode(base64.b64encode(ud))
|
|
|
|
sgid = self._get_security_group(obj)
|
|
kwargs['security_groups'] = [{'name': sgid}]
|
|
|
|
server = None
|
|
resource_id = None
|
|
try:
|
|
server = self.compute(obj).server_create(**kwargs)
|
|
self.compute(obj).wait_for_server(server.id)
|
|
server = self.compute(obj).server_get(server.id)
|
|
self._update_master_ip(obj, server.addresses[''][0]['addr'])
|
|
self._associate_floatingip(obj, server)
|
|
LOG.info("Created master node: %s" % server.id)
|
|
return server.id
|
|
except exc.InternalError as ex:
|
|
if server and server.id:
|
|
resource_id = server.id
|
|
raise exc.EResourceCreation(type='server',
|
|
message=str(ex),
|
|
resource_id=resource_id)
|
|
|
|
def do_delete(self, obj, **params):
|
|
"""Delete the physical resource associated with the specified node.
|
|
|
|
:param obj: The node object to operate on.
|
|
:param kwargs params: Optional keyword arguments for the delete
|
|
operation.
|
|
:returns: This operation always return True unless exception is
|
|
caught.
|
|
:raises: `EResourceDeletion` if interaction with compute service fails.
|
|
"""
|
|
if not obj.physical_id:
|
|
return True
|
|
|
|
server_id = obj.physical_id
|
|
ignore_missing = params.get('ignore_missing', True)
|
|
internal_ports = obj.data.get('internal_ports', [])
|
|
force = params.get('force', False)
|
|
|
|
try:
|
|
self._disassociate_floatingip(obj, server_id)
|
|
driver = self.compute(obj)
|
|
if force:
|
|
driver.server_force_delete(server_id, ignore_missing)
|
|
else:
|
|
driver.server_delete(server_id, ignore_missing)
|
|
driver.wait_for_server_delete(server_id)
|
|
if internal_ports:
|
|
ex = self._delete_ports(obj, internal_ports)
|
|
if ex:
|
|
raise ex
|
|
return True
|
|
except exc.InternalError as ex:
|
|
raise exc.EResourceDeletion(type='server', id=server_id,
|
|
message=str(ex))
|