Skip to content

Commit 14d77a6

Browse files
committed
Add client to talk to RackConnect api
This patch adds a simple client to talk with the rackconnect api. Currently, it only deals with adding/removing dedicated loadbalancers and assigning/removing public IP's to cloudservers.
1 parent 7dc454c commit 14d77a6

File tree

5 files changed

+664
-1
lines changed

5 files changed

+664
-1
lines changed

pyrax/__init__.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
from .image import ImageClient
7171
from .object_storage import StorageClient
7272
from .queueing import QueueClient
73+
from .rackconnect import RackConnectClient
7374
except ImportError:
7475
# See if this is the result of the importing of version.py in setup.py
7576
callstack = inspect.stack()
@@ -93,6 +94,7 @@
9394
autoscale = None
9495
images = None
9596
queues = None
97+
rackconnect = None
9698
# Default region for all services. Can be individually overridden if needed
9799
default_region = None
98100
# Encoding to use when working with non-ASCII names
@@ -125,6 +127,7 @@
125127
"autoscale": AutoScaleClient,
126128
"image": ImageClient,
127129
"queues": QueueClient,
130+
"rackconnect": RackConnectClient,
128131
}
129132

130133

@@ -581,6 +584,7 @@ def clear_credentials():
581584
global identity, regions, services, cloudservers, cloudfiles
582585
global cloud_loadbalancers, cloud_databases, cloud_blockstorage, cloud_dns
583586
global cloud_networks, cloud_monitoring, autoscale, images, queues
587+
global rackconnect
584588
identity = None
585589
regions = tuple()
586590
services = tuple()
@@ -595,6 +599,7 @@ def clear_credentials():
595599
autoscale = None
596600
images = None
597601
queues = None
602+
rackconnect = None
598603

599604

600605
def _make_agent_name(base):
@@ -612,7 +617,7 @@ def connect_to_services(region=None):
612617
"""Establishes authenticated connections to the various cloud APIs."""
613618
global cloudservers, cloudfiles, cloud_loadbalancers, cloud_databases
614619
global cloud_blockstorage, cloud_dns, cloud_networks, cloud_monitoring
615-
global autoscale, images, queues
620+
global autoscale, images, queues, rackconnect
616621
cloudservers = connect_to_cloudservers(region=region)
617622
cloudfiles = connect_to_cloudfiles(region=region)
618623
cloud_loadbalancers = connect_to_cloud_loadbalancers(region=region)
@@ -624,6 +629,7 @@ def connect_to_services(region=None):
624629
autoscale = connect_to_autoscale(region=region)
625630
images = connect_to_images(region=region)
626631
queues = connect_to_queues(region=region)
632+
rackconnect = connect_to_rackconnect(region=region)
627633

628634

629635
def _get_service_endpoint(context, svc, region=None, public=True):
@@ -739,6 +745,8 @@ def _create_client(ep_name, region, public=True):
739745
client = cls(identity, region_name=region, management_url=ep,
740746
verify_ssl=verify_ssl, http_log_debug=_http_debug)
741747
client.user_agent = _make_agent_name(client.user_agent)
748+
if client is None:
749+
print("client %s is None" % ep_name)
742750
return client
743751

744752

@@ -787,6 +795,11 @@ def connect_to_queues(region=None, public=True):
787795
return _create_client(ep_name="queues", region=region, public=public)
788796

789797

798+
def connect_to_rackconnect(region=None):
799+
"""Creates a client for working with RackConnect."""
800+
return _create_client(ep_name="rackconnect", region=region)
801+
802+
790803
def client_class_for_service(service):
791804
"""
792805
Returns the client class registered for the given service, or None if there

pyrax/exceptions.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,16 @@ class NoUniqueMatch(ClientException):
422422
message = "Not Unique"
423423

424424

425+
class Conflict(ClientException):
426+
"""
427+
HTTP 409 - Conflict
428+
"""
429+
def __init__(self, message, *args, **kwargs):
430+
code = 409
431+
message = message or "Conflict"
432+
super(Conflict, self).__init__(code, message, *args, **kwargs)
433+
434+
425435
class OverLimit(ClientException):
426436
"""
427437
HTTP 413 - Over limit: you're over the API limits for this time period.

pyrax/manager.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ def _data_from_response(self, resp_body, key=None):
171171
listing responses the same way, so overriding this method allows
172172
subclasses to handle extraction for those outliers.
173173
"""
174+
if self.plural_response_key is None:
175+
return resp_body
174176
if key:
175177
data = resp_body.get(key)
176178
else:

pyrax/rackconnect.py

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Copyright (c) 2014 Rackspace US, Inc.
4+
5+
# All Rights Reserved.
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
8+
# not use this file except in compliance with the License. You may obtain
9+
# a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16+
# License for the specific language governing permissions and limitations
17+
# under the License.
18+
19+
from pyrax.client import BaseClient
20+
import pyrax.exceptions as exc
21+
from pyrax.manager import BaseManager
22+
from pyrax.resource import BaseResource
23+
import pyrax.utils as utils
24+
25+
26+
class Network(BaseResource):
27+
"""A rackconnected cloudnetwork instance."""
28+
pass
29+
30+
31+
class LoadBalancerPool(BaseResource):
32+
"""A pool of nodes that are Load-Balanced."""
33+
def nodes(self):
34+
return self.manager.get_pool_nodes(self)
35+
36+
def add_node(self, server):
37+
self.manager.add_pool_node(self, server)
38+
39+
40+
class PoolNode(BaseResource):
41+
"""A node in a LoadBalancerPool."""
42+
def get_pool(self):
43+
return self.manager.get(self.load_balancer_pool['id'])
44+
45+
def get(self):
46+
"""Gets the details for the object."""
47+
# set 'loaded' first ... so if we have to bail, we know we tried.
48+
self.loaded = True
49+
if not hasattr(self.manager, "get"):
50+
return
51+
if not self.get_details:
52+
return
53+
54+
pool = self.get_pool()
55+
new = self.manager.get_pool_node(pool, self)
56+
if new:
57+
self._add_details(new._info)
58+
59+
60+
class PublicIP(BaseResource):
61+
"""Represents Public IP's assigned to RackConnected servers."""
62+
pass
63+
64+
65+
class LoadBalancerPoolManager(BaseManager):
66+
67+
def _get_node_base_uri(self, pool, node=None):
68+
if node is not None:
69+
template = "/%s/%s/nodes/%s"
70+
params = (self.uri_base, utils.get_id(pool), utils.get_id(node))
71+
else:
72+
template = "/%s/%s/nodes"
73+
params = (self.uri_base, utils.get_id(pool))
74+
return template % params
75+
76+
def _make_pool_node_body(self, pool, server):
77+
return {
78+
'cloud_server': {
79+
'id': utils.get_id(server)
80+
},
81+
'load_balancer_pool': {
82+
'id': utils.get_id(pool),
83+
}
84+
}
85+
86+
def get_pool_node(self, pool, node):
87+
if isinstance(node, PoolNode):
88+
return node
89+
uri = self._get_node_base_uri(pool, node=node)
90+
resp, resp_body = self.api.method_get(uri)
91+
return PoolNode(self, resp_body, loaded=True)
92+
93+
def get_pool_nodes(self, pool):
94+
uri = self._get_node_base_uri(pool)
95+
resp, resp_body = self.api.method_get(uri)
96+
return [PoolNode(self, node, loaded=True)
97+
for node in resp_body if node]
98+
99+
def add_pool_node(self, pool, server):
100+
pool_id = utils.get_id(pool)
101+
uri = self._get_node_base_uri(pool_id)
102+
body = self._make_pool_node_body(pool, server)
103+
resp, resp_body = self.api.method_post(uri, body=body)
104+
return PoolNode(self, resp_body, loaded=True)
105+
106+
def add_pool_nodes(self, pool_map):
107+
uri = "/%s/nodes" % self.uri_base
108+
body = [self._make_pool_node_body(pool, server)
109+
for pool, server in pool_map.items()]
110+
resp, resp_body = self.api.method_post(uri, body=body)
111+
return [PoolNode(self, res, loaded=True) for res in resp_body]
112+
113+
def delete_pool_node(self, pool, node):
114+
uri = self._get_node_base_uri(pool, node=node)
115+
resp, resp_body = self.api.method_delete(uri)
116+
try:
117+
return self.get_pool_node(pool, node)
118+
except exc.NotFound:
119+
return
120+
121+
122+
class PublicIPManager(BaseManager):
123+
124+
def get_server(self, server):
125+
uri = "/%s?cloud_server_id=%s" % (self.uri_base, utils.get_id(server))
126+
resp, resp_body = self.api.method_get(uri)
127+
return [PublicIP(self, res, loaded=True) for res in resp_body]
128+
129+
def add_public_ip(self, server):
130+
uri = "/%s" % (self.uri_base)
131+
body = {
132+
'cloud_server': {
133+
'id': utils.get_id(server),
134+
},
135+
}
136+
resp, resp_body = self.api.method_post(uri, body=body)
137+
return PublicIP(self, resp_body, loaded=True)
138+
139+
def delete_public_ip(self, public_ip):
140+
uri = "/%s/%s" % (self.uri_base, utils.get_id(public_ip))
141+
resp, resp_body = self.api.method_delete(uri)
142+
try:
143+
return self.get(public_ip)
144+
except exc.NotFound:
145+
return
146+
147+
148+
class RackConnectClient(BaseClient):
149+
"""A client to interact with RackConnected resources."""
150+
151+
name = "RackConnect"
152+
153+
def _configure_manager(self):
154+
"""Create a manager to handle RackConnect operations."""
155+
self._network_manager = BaseManager(
156+
self, resource_class=Network, uri_base="cloud_networks",
157+
)
158+
self._load_balancer_pool_manager = LoadBalancerPoolManager(
159+
self, resource_class=LoadBalancerPool,
160+
uri_base="load_balancer_pools"
161+
)
162+
self._public_ip_manager = PublicIPManager(
163+
self, resource_class=PublicIP, uri_base="public_ips",
164+
)
165+
166+
def get_network(self, network):
167+
return self._network_manager.get(network)
168+
169+
def list_networks(self):
170+
return self._network_manager.list()
171+
172+
def list_load_balancer_pools(self):
173+
return self._load_balancer_pool_manager.list()
174+
175+
def get_load_balancer_pool(self, pool):
176+
return self._load_balancer_pool_manager.get(pool)
177+
178+
def list_pool_nodes(self, pool):
179+
return self._load_balancer_pool_manager.get_pool_nodes(pool)
180+
181+
def create_pool_node(self, pool, server):
182+
return self._load_balancer_pool_manager.add_pool_node(pool, server)
183+
184+
def delete_pool_node(self, pool, node):
185+
return self._load_balancer_pool_manager.delete_pool_node(pool, node)
186+
187+
def list_public_ips(self):
188+
return self._public_ip_manager.list()
189+
190+
def get_public_ip(self, public_ip):
191+
return self._public_ip_manager.get(public_ip)
192+
193+
def get_public_ips_server(self, server):
194+
return self._public_ip_manager.get_server(server)
195+
196+
def delete_public_ip(self, public_ip):
197+
return self._public_ip_manager.delete_public_ip(public_ip)
198+
199+
def add_public_ip(self, server):
200+
return self._public_ip_manager.add_public_ip(server)
201+
202+
#################################################################
203+
# The following methods are defined in the generic client class,
204+
# but don't have meaning in RackConnect, as there is not a single
205+
# resource that defines this module.
206+
#################################################################
207+
def list(self, limit=None, marker=None):
208+
"""Not applicable in RackConnect."""
209+
raise NotImplementedError
210+
211+
def get(self, item):
212+
"""Not applicable in RackConnect."""
213+
raise NotImplementedError
214+
215+
def create(self, *args, **kwargs):
216+
"""Not applicable in RackConnect."""
217+
raise NotImplementedError
218+
219+
def delete(self, item):
220+
"""Not applicable in RackConnect."""
221+
raise NotImplementedError
222+
223+
def find(self, **kwargs):
224+
"""Not applicable in RackConnect."""
225+
raise NotImplementedError
226+
227+
def findall(self, **kwargs):
228+
"""Not applicable in RackConnect."""
229+
raise NotImplementedError

0 commit comments

Comments
 (0)