210 lines
7.6 KiB
Python
210 lines
7.6 KiB
Python
# Licensed to the Apache Software Foundation (ASF) under one or more
|
|
# contributor license agreements. See the NOTICE file distributed with
|
|
# this work for additional information regarding copyright ownership.
|
|
# The ASF licenses this file to You 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 libcloud.common.cloudstack import CloudStackDriverMixIn
|
|
from libcloud.loadbalancer.base import LoadBalancer, Member, Driver, Algorithm
|
|
from libcloud.loadbalancer.base import DEFAULT_ALGORITHM
|
|
from libcloud.loadbalancer.types import Provider
|
|
from libcloud.loadbalancer.types import State
|
|
from libcloud.utils.misc import reverse_dict
|
|
|
|
|
|
class CloudStackLBDriver(CloudStackDriverMixIn, Driver):
|
|
"""Driver for CloudStack load balancers."""
|
|
|
|
api_name = 'cloudstack_lb'
|
|
name = 'CloudStack'
|
|
website = 'http://cloudstack.org/'
|
|
type = Provider.CLOUDSTACK
|
|
|
|
_VALUE_TO_ALGORITHM_MAP = {
|
|
'roundrobin': Algorithm.ROUND_ROBIN,
|
|
'leastconn': Algorithm.LEAST_CONNECTIONS
|
|
}
|
|
_ALGORITHM_TO_VALUE_MAP = reverse_dict(_VALUE_TO_ALGORITHM_MAP)
|
|
|
|
LB_STATE_MAP = {
|
|
'Active': State.RUNNING,
|
|
}
|
|
|
|
def __init__(self, key, secret=None, secure=True, host=None,
|
|
path=None, port=None, *args, **kwargs):
|
|
"""
|
|
@inherits: :class:`Driver.__init__`
|
|
"""
|
|
host = host if host else self.host
|
|
path = path if path else self.path
|
|
|
|
if path is not None:
|
|
self.path = path
|
|
|
|
if host is not None:
|
|
self.host = host
|
|
|
|
if (self.type == Provider.CLOUDSTACK) and (not host or not path):
|
|
raise Exception('When instantiating CloudStack driver directly ' +
|
|
'you also need to provide host and path argument')
|
|
|
|
super(CloudStackLBDriver, self).__init__(key=key, secret=secret,
|
|
secure=secure,
|
|
host=host, port=port)
|
|
|
|
def list_protocols(self):
|
|
"""
|
|
We don't actually have any protocol awareness beyond TCP.
|
|
|
|
:rtype: ``list`` of ``str``
|
|
"""
|
|
return ['tcp']
|
|
|
|
def list_balancers(self):
|
|
balancers = self._sync_request(command='listLoadBalancerRules',
|
|
method='GET')
|
|
balancers = balancers.get('loadbalancerrule', [])
|
|
return [self._to_balancer(balancer) for balancer in balancers]
|
|
|
|
def get_balancer(self, balancer_id):
|
|
balancer = self._sync_request(command='listLoadBalancerRules',
|
|
params={'id': balancer_id},
|
|
method='GET')
|
|
balancer = balancer.get('loadbalancerrule', [])
|
|
if not balancer:
|
|
raise Exception("no such load balancer: " + str(balancer_id))
|
|
return self._to_balancer(balancer[0])
|
|
|
|
def create_balancer(self, name, members, protocol='http', port=80,
|
|
algorithm=DEFAULT_ALGORITHM, location=None,
|
|
private_port=None, network_id=None, vpc_id=None):
|
|
"""
|
|
@inherits: :class:`Driver.create_balancer`
|
|
|
|
:param location: Location
|
|
:type location: :class:`NodeLocation`
|
|
|
|
:param private_port: Private port
|
|
:type private_port: ``int``
|
|
|
|
:param network_id: The guest network this rule will be created for.
|
|
:type network_id: ``str``
|
|
"""
|
|
|
|
args = {}
|
|
ip_args = {}
|
|
|
|
if location is None:
|
|
locations = self._sync_request(command='listZones', method='GET')
|
|
location = locations['zone'][0]['id']
|
|
else:
|
|
location = location.id
|
|
if private_port is None:
|
|
private_port = port
|
|
|
|
if network_id is not None:
|
|
args['networkid'] = network_id
|
|
ip_args['networkid'] = network_id
|
|
|
|
if vpc_id is not None:
|
|
ip_args['vpcid'] = vpc_id
|
|
|
|
ip_args.update({'zoneid': location,
|
|
'networkid': network_id,
|
|
'vpc_id': vpc_id})
|
|
|
|
result = self._async_request(command='associateIpAddress',
|
|
params=ip_args,
|
|
method='GET')
|
|
public_ip = result['ipaddress']
|
|
|
|
args.update({'algorithm': self._ALGORITHM_TO_VALUE_MAP[algorithm],
|
|
'name': name,
|
|
'privateport': private_port,
|
|
'publicport': port,
|
|
'publicipid': public_ip['id']})
|
|
|
|
result = self._sync_request(
|
|
command='createLoadBalancerRule',
|
|
params=args,
|
|
method='GET')
|
|
|
|
listbalancers = self._sync_request(
|
|
command='listLoadBalancerRules',
|
|
params=args,
|
|
method='GET')
|
|
|
|
listbalancers = [rule for rule in listbalancers['loadbalancerrule'] if
|
|
rule['id'] == result['id']]
|
|
if len(listbalancers) != 1:
|
|
return None
|
|
|
|
balancer = self._to_balancer(listbalancers[0])
|
|
|
|
for member in members:
|
|
balancer.attach_member(member)
|
|
|
|
return balancer
|
|
|
|
def destroy_balancer(self, balancer):
|
|
self._async_request(command='deleteLoadBalancerRule',
|
|
params={'id': balancer.id},
|
|
|
|
method='GET')
|
|
self._async_request(command='disassociateIpAddress',
|
|
params={'id': balancer.ex_public_ip_id},
|
|
method='GET')
|
|
|
|
def balancer_attach_member(self, balancer, member):
|
|
member.port = balancer.ex_private_port
|
|
self._async_request(command='assignToLoadBalancerRule',
|
|
params={'id': balancer.id,
|
|
'virtualmachineids': member.id},
|
|
method='GET')
|
|
return True
|
|
|
|
def balancer_detach_member(self, balancer, member):
|
|
self._async_request(command='removeFromLoadBalancerRule',
|
|
params={'id': balancer.id,
|
|
'virtualmachineids': member.id},
|
|
method='GET')
|
|
return True
|
|
|
|
def balancer_list_members(self, balancer):
|
|
members = self._sync_request(command='listLoadBalancerRuleInstances',
|
|
params={'id': balancer.id},
|
|
method='GET')
|
|
members = members['loadbalancerruleinstance']
|
|
return [self._to_member(m, balancer.ex_private_port, balancer)
|
|
for m in members]
|
|
|
|
def _to_balancer(self, obj):
|
|
balancer = LoadBalancer(
|
|
id=obj['id'],
|
|
name=obj['name'],
|
|
state=self.LB_STATE_MAP.get(obj['state'], State.UNKNOWN),
|
|
ip=obj['publicip'],
|
|
port=obj['publicport'],
|
|
driver=self.connection.driver
|
|
)
|
|
balancer.ex_private_port = obj['privateport']
|
|
balancer.ex_public_ip_id = obj['publicipid']
|
|
return balancer
|
|
|
|
def _to_member(self, obj, port, balancer):
|
|
return Member(
|
|
id=obj['id'],
|
|
ip=obj['nic'][0]['ipaddress'],
|
|
port=port,
|
|
balancer=balancer
|
|
)
|