heat/contrib/rackspace/rackspace/resources/lb_node.py

231 lines
6.3 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 datetime
from oslo_utils import timeutils
import six
from heat.common import exception
from heat.common.i18n import _
from heat.engine import constraints
from heat.engine import properties
from heat.engine import resource
try:
from pyrax.exceptions import NotFound # noqa
PYRAX_INSTALLED = True
except ImportError:
# Setup fake exception for testing without pyrax
class NotFound(Exception):
pass
PYRAX_INSTALLED = False
def lb_immutable(exc):
return 'immutable' in six.text_type(exc)
class LoadbalancerDeleted(exception.HeatException):
msg_fmt = _("The Load Balancer (ID %(lb_id)s) has been deleted.")
class NodeNotFound(exception.HeatException):
msg_fmt = _("Node (ID %(node_id)s) not found on Load Balancer "
"(ID %(lb_id)s).")
class LBNode(resource.Resource):
"""Represents a single node of a Rackspace Cloud Load Balancer"""
default_client_name = 'cloud_lb'
_CONDITIONS = (
ENABLED, DISABLED, DRAINING,
) = (
'ENABLED', 'DISABLED', 'DRAINING',
)
_NODE_KEYS = (
ADDRESS, PORT, CONDITION, TYPE, WEIGHT
) = (
'address', 'port', 'condition', 'type', 'weight'
)
_OTHER_KEYS = (
LOAD_BALANCER, DRAINING_TIMEOUT
) = (
'load_balancer', 'draining_timeout'
)
PROPERTIES = _NODE_KEYS + _OTHER_KEYS
properties_schema = {
LOAD_BALANCER: properties.Schema(
properties.Schema.STRING,
_("The ID of the load balancer to associate the node with."),
required=True
),
DRAINING_TIMEOUT: properties.Schema(
properties.Schema.INTEGER,
_("The time to wait, in seconds, for the node to drain before it "
"is deleted."),
default=0,
constraints=[
constraints.Range(min=0)
],
update_allowed=True
),
ADDRESS: properties.Schema(
properties.Schema.STRING,
_("IP address for the node."),
required=True
),
PORT: properties.Schema(
properties.Schema.INTEGER,
required=True
),
CONDITION: properties.Schema(
properties.Schema.STRING,
default=ENABLED,
constraints=[
constraints.AllowedValues(_CONDITIONS),
],
update_allowed=True
),
TYPE: properties.Schema(
properties.Schema.STRING,
constraints=[
constraints.AllowedValues(['PRIMARY',
'SECONDARY']),
],
update_allowed=True
),
WEIGHT: properties.Schema(
properties.Schema.NUMBER,
constraints=[
constraints.Range(1, 100),
],
update_allowed=True
),
}
def lb(self):
lb_id = self.properties.get(self.LOAD_BALANCER)
lb = self.client().get(lb_id)
if lb.status in ('DELETED', 'PENDING_DELETE'):
raise LoadbalancerDeleted(lb_id=lb.id)
return lb
def node(self, lb):
for node in getattr(lb, 'nodes', []):
if node.id == self.resource_id:
return node
raise NodeNotFound(node_id=self.resource_id, lb_id=lb.id)
def handle_create(self):
pass
def check_create_complete(self, *args):
node_args = {k: self.properties.get(k) for k in self._NODE_KEYS}
node = self.client().Node(**node_args)
try:
resp, body = self.lb().add_nodes([node])
except Exception as exc:
if lb_immutable(exc):
return False
raise
new_node = body['nodes'][0]
node_id = new_node['id']
self.resource_id_set(node_id)
return True
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
return prop_diff
def check_update_complete(self, prop_diff):
node = self.node(self.lb())
is_complete = True
for key in self._NODE_KEYS:
if key in prop_diff and getattr(node, key, None) != prop_diff[key]:
setattr(node, key, prop_diff[key])
is_complete = False
if is_complete:
return True
try:
node.update()
except Exception as exc:
if lb_immutable(exc):
return False
raise
return False
def handle_delete(self):
return timeutils.utcnow()
def check_delete_complete(self, deleted_at):
if self.resource_id is None:
return True
try:
node = self.node(self.lb())
except (NotFound, LoadbalancerDeleted, NodeNotFound):
return True
if isinstance(deleted_at, six.string_types):
deleted_at = timeutils.parse_isotime(deleted_at)
deleted_at = timeutils.normalize_time(deleted_at)
waited = timeutils.utcnow() - deleted_at
timeout_secs = self.properties[self.DRAINING_TIMEOUT]
timeout_secs = datetime.timedelta(seconds=timeout_secs)
if waited > timeout_secs:
try:
node.delete()
except NotFound:
return True
except Exception as exc:
if lb_immutable(exc):
return False
raise
elif node.condition != self.DRAINING:
node.condition = self.DRAINING
try:
node.update()
except Exception as exc:
if lb_immutable(exc):
return False
raise
return False
def resource_mapping():
return {'Rackspace::Cloud::LBNode': LBNode}
def available_resource_mapping():
if PYRAX_INSTALLED:
return resource_mapping()
return {}