Security Groups and Server Persona Add-ons

- Adding a get remote instance client wrapper for the compute method
to the networks behaviors.
- Adding the PortUpdate Exception
- Adding the add_rule method to the security groups behaviors
- Adding the remove_rule method to the security groups behaviors
- Adding security groups to the server persona
- Adding add_security_groups_to_ports to the server persona
- Adding remote client to server persona

Change-Id: Ib117da573ea4f3a2ce1e7d9614cbb391b322ffef
This commit is contained in:
Leonardo Maycotte 2016-10-20 11:44:51 -05:00
parent 93f15d674d
commit 947a27eb59
4 changed files with 265 additions and 9 deletions

View File

@ -361,6 +361,21 @@ class NetworkingBehaviors(NetworkingBaseBehaviors):
resp.entity.admin_pass = server.admin_pass
return resp.entity
def get_remote_instance_client(self, server, ip_address, private_key,
ssh_username='root', auth_strategy='key'):
"""
@summary: gets a compute server remote client
"""
if self.compute is None:
raise UnavailableComputeInteractionException
remote_client = (
self.compute.servers.behaviors.get_remote_instance_client(
server=server, ip_address=ip_address, username=ssh_username,
key=private_key, auth_strategy=auth_strategy))
return remote_client
def list_servers(self, name=None, raise_exception=False):
"""
@summary: list servers wrapper for networks

View File

@ -75,6 +75,10 @@ class ResourceUpdateException(BaseNetworkingException):
MSG = 'Unable to update resource'
class PortUpdateException(BaseNetworkingException):
MSG = 'Unable to update port'
class ResourceGetException(BaseNetworkingException):
MSG = 'Unable to get resource'

View File

@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
"""
import copy
import time
from cloudcafe.common.tools.datagen import rand_name
@ -21,18 +22,137 @@ from cloudcafe.networking.networks.common.behaviors \
import NetworkingBaseBehaviors, NetworkingResponse
from cloudcafe.networking.networks.common.exceptions \
import ResourceBuildException, ResourceDeleteException, \
ResourceGetException, ResourceListException, ResourceUpdateException
ResourceGetException, ResourceListException, \
ResourceUpdateException, MissingDataException, \
UnsupportedTypeException
from cloudcafe.networking.networks.extensions.security_groups_api.constants \
import SecurityGroupsResponseCodes
class SecurityGroupsBehaviors(NetworkingBaseBehaviors):
ICMP = 'icmp'
TCP = 'tcp'
UDP = 'udp'
def __init__(self, security_groups_client, security_groups_config):
super(SecurityGroupsBehaviors, self).__init__()
self.config = security_groups_config
self.client = security_groups_client
def add_rule(self, security_groups, version=4, protocol='tcp', ports=None,
ingress=True, egress=False):
"""
@summary: Create security group rules ingress/egress
@param security_groups: security groups to add the rule.
@type security_groups: list
@param version: IP version, 4 or 6.
@type version: int
@param protocol: icmp, tcp or udp.
@type protocol: str
@param ports: port ranges min and max, for ex. 442-445, also only one
can be given for both, for ex. 22
@type ports: str
@param ingress: flag for adding an ingress rule.
@type ingress: bool
@param egress: flag for adding an egress rule.
@type egress: bool
"""
# Verifying an expected protocol is given
expected_protocols = [self.ICMP, self.TCP, self.UDP]
if protocol not in expected_protocols:
msg = '{0} not within the expected protocols: {1}'.format(
protocol, expected_protocols)
raise MissingDataException(msg)
# Setting the rule protocol and ethertype from the IP version
ethertype = 'IPv{0}'.format(version)
attrs_kwargs = dict(protocol=protocol, ethertype=ethertype)
# Verifying a port or port range is given for tcp and udp rules
if protocol in [self.TCP, self.UDP]:
if not ports:
msg = ('{0} protocol requires the ports value, for ex.'
'22 or 442-445').format(protocol)
raise MissingDataException(msg)
# In case only one port is given as an int
if type(ports) == int:
ports = str(ports)
elif type(ports) != str:
msg = ('{0} should be a string for ex. "22" or '
'"442-445"').format(ports)
raise UnsupportedTypeException(msg)
port_list = ports.split('-')
port_range_min = port_list[0]
port_range_max = port_list[-1]
# Adding the port ranges to the request attributes
attrs_kwargs.update(port_range_min=port_range_min)
attrs_kwargs.update(port_range_max=port_range_max)
results = []
# Adding the ingress/egress rules to the security groups given.
for sg_id in security_groups:
result = dict(security_group_id=sg_id)
request_kwargs = copy.deepcopy(attrs_kwargs)
request_kwargs.update(security_group_id=sg_id)
if ingress:
request_kwargs.update(direction='ingress')
rule = self.create_security_group_rule(**request_kwargs)
result.update(ingress_rule_request=rule)
if egress:
request_kwargs.update(direction='egress')
rule = self.create_security_group_rule(**request_kwargs)
result.update(egress_rule_request=rule)
results.append(result)
return results
def remove_rule(self, security_groups, version=None, protocol='',
direction='', all_rules=False):
"""
@summary: remove rules from groups based on criteria
@param security_groups: security groups to remove the rules.
@type security_groups: list
@param version: rules with this IP version will be deleted if given.
@type version: int
@param protocol: rules with this protocol will be deleted if given.
@type protocol: str
@param direction: rules with this direction will be deleted if given.
@type direction: str
@param all_rules: flag to delete all rules within the security group.
@type all_rules: bool
"""
if version:
ethertype = 'IPv{0}'.format(version)
else:
ethertype = 'DoNotDeleteByIP'
results = []
# Removing rules
for sg_id in security_groups:
sec_group = self.get_security_group(security_group_id=sg_id,
raise_exception=True)
rules = sec_group.response.entity.security_group_rules
for rule in rules:
result = dict(security_group_id=sg_id,
security_group_rule_id=rule.id)
if (all_rules or rule.ethertype.lower() == ethertype.lower() or
rule.protocol.lower() == protocol.lower() or
rule.direction.lower() == direction.lower()):
delete = self.delete_security_group_rule(
security_group_rule_id=rule.id, raise_exception=True)
result.update(delete_request=delete)
results.append(result)
return results
def create_security_group(self, name=None, description=None,
tenant_id=None, resource_build_attempts=None,
raise_exception=True, use_exact_name=False,
@ -587,9 +707,10 @@ class SecurityGroupsBehaviors(NetworkingBaseBehaviors):
resp = self.client.get_security_group_rule(
security_group_rule_id=security_group_rule_id)
status_code = SecurityGroupsResponseCodes.GET_SECURITY_GROUP_RULE
resp_check = self.check_response(
resp=resp,
status_code=SecurityGroupsResponseCodes.GET_SECURITY_GROUP_RULE,
status_code=status_code,
label=security_group_rule_id, message=err_msg)
result.response = resp
@ -678,9 +799,10 @@ class SecurityGroupsBehaviors(NetworkingBaseBehaviors):
remote_ip_prefix=remote_ip_prefix, tenant_id=tenant_id,
limit=limit, marker=marker, page_reverse=page_reverse)
status_code = SecurityGroupsResponseCodes.LIST_SECURITY_GROUP_RULES
resp_check = self.check_response(
resp=resp,
status_code=SecurityGroupsResponseCodes.LIST_SECURITY_GROUP_RULES,
status_code=status_code,
label='', message=err_msg)
result.response = resp

View File

@ -19,6 +19,11 @@ from cloudcafe.networking.networks.common.behaviors \
from cloudcafe.networking.networks.common.constants \
import NetworkTypes, PortTypes
from cloudcafe.networking.networks.composites import NetworkingComposite
from cloudcafe.networking.networks.extensions.security_groups_api.composites \
import SecurityGroupsComposite
from cloudcafe.networking.networks.common.exceptions \
import PortUpdateException
class ServerPersona(BaseModel, NetworkingBaseBehaviors):
@ -32,7 +37,7 @@ class ServerPersona(BaseModel, NetworkingBaseBehaviors):
pnet_port_count=1, inet_fix_ipv4_count=0,
inet_fix_ipv6_count=0, snet_fix_ipv4_count=1,
snet_fix_ipv6_count=0, pnet_fix_ipv4_count=1,
pnet_fix_ipv6_count=1):
pnet_fix_ipv6_count=1, keypair=None, ssh_username=None):
super(ServerPersona, self).__init__()
"""
@param server: server entity
@ -74,10 +79,16 @@ class ServerPersona(BaseModel, NetworkingBaseBehaviors):
@type pnet_fix_ipv4_count: int
@param pnet_fix_ipv6_count: expected public network fixed IPv6s count
@type pnet_fix_ipv6_count: int
@param keypair: keypair object with private_key attribute
@type keypair: networking.networks.behaviors create_keypair response
@param ssh_username: remote client username for SSH
@type ssh_username: str
"""
# Server entity object
# Server and keypair entity object
self.server = server
self.keypair = keypair
self.ssh_username = ssh_username
# Server expected networks (bool value)
self.pnet = pnet
@ -104,8 +115,9 @@ class ServerPersona(BaseModel, NetworkingBaseBehaviors):
self.pnet_fix_ipv4_count = pnet_fix_ipv4_count
self.pnet_fix_ipv6_count = pnet_fix_ipv6_count
# Networking composite
# Networking composites
self.net = NetworkingComposite()
self.sec = SecurityGroupsComposite()
# base config from networking/networks/common/config.py
self.config = self.net.config
@ -128,6 +140,8 @@ class ServerPersona(BaseModel, NetworkingBaseBehaviors):
'network {1}. Failures: {2}.')
self.fixed_ips_failure_msg = ('Unable to get server {0} fixed IPs '
'with IPv{1} version for network {2}')
self.update_port_failure_msg = ('Unable to update server {0} ports for'
' network {1}. Failures: {2}.')
def __str__(self):
@ -142,32 +156,37 @@ class ServerPersona(BaseModel, NetworkingBaseBehaviors):
return ', '.join(data_str)
data = {'name': self.server.name, 'svr_id':self.server.id,
data = {'name': self.server.name, 'svr_id': self.server.id,
'pub_net_id': self.public_network_id,
'pub_port_ids': self.pnet_port_ids,
'pub_sec_groups': self.pnet_security_groups,
'pub_ipv4_addr': self.pnet_fix_ipv4,
'pub_ipv6_addr': self.pnet_fix_ipv6,
'svc_net_id': self.service_network_id,
'svc_port_ids': self.snet_port_ids,
'svc_sec_groups': self.snet_security_groups,
'svc_ipv4_addr': self.snet_fix_ipv4,
'svc_ipv6_addr': self.snet_fix_ipv6,
'iso_net_id': getattr(self.network, 'id', None),
'iso_sub_id': getattr(self.subnetv4, 'id', None),
'iso_port_ids': self.inet_port_ids,
'iso_ipv4_addr': self.pnet_fix_ipv4,
'iso_sec_groups': self.inet_security_groups,
'iso_ipv4_addr': self.inet_fix_ipv4,
'iso_sub_v6_ids': build_data_str(self.subnetv6, 'id'),
'iso_ipv6_addr': self.pnet_fix_ipv6}
'iso_ipv6_addr': self.inet_fix_ipv6}
msg = "\nServer Name: {name} ({svr_id})\n"
msg += "Public Net:\n"
msg += "\tNetwork Id: {pub_net_id}\n"
msg += "\tPort Ids: {pub_port_ids}\n"
msg += "\tSecurity Groups: {pub_sec_groups}\n"
msg += "\tIPv4 Address: {pub_ipv4_addr}\n"
msg += "\tIPv6 Address: {pub_ipv6_addr}\n\n"
msg += "Service Net:"
msg += "\tNetwork Id: {svc_net_id}\n"
msg += "\tPort Ids: {svc_port_ids}\n"
msg += "\tSecurity Groups: {svc_sec_groups}\n"
msg += "\tIPv4 Address: {svc_ipv4_addr}\n"
msg += "\tIPv6 Address: {svc_ipv6_addr}\n\n"
@ -175,6 +194,7 @@ class ServerPersona(BaseModel, NetworkingBaseBehaviors):
msg += "\tNetwork Id: {iso_net_id}\n"
msg += "\tSubnet Id (IPv4): {iso_sub_id}\n"
msg += "\tPort Ids: {iso_port_ids}\n"
msg += "\tSecurity Groups: {iso_sec_groups}\n"
msg += "\tIPv4 Address: {iso_ipv4_addr}\n"
msg += "\tSubnet Id (IPv6): {iso_sub_v6_ids}\n"
msg += "\tIPv6 Address: {iso_ipv6_addr}\n\n"
@ -289,6 +309,34 @@ class ServerPersona(BaseModel, NetworkingBaseBehaviors):
"""
return self._get_port_ids(port_type=PortTypes.ISOLATED)
@property
def pnet_security_groups(self):
"""
@summary: gets the security groups at the public network ports
"""
return self._get_port_security_groups(port_type=PortTypes.PUBLIC)
@property
def snet_security_groups(self):
"""
@summary: gets the security groups at the service network ports
"""
return self._get_port_security_groups(port_type=PortTypes.SERVICE)
@property
def inet_security_groups(self):
"""
@summary: gets the security groups at the isolated network ports
"""
return self._get_port_security_groups(port_type=PortTypes.ISOLATED)
@property
def remote_client(self):
"""
@summary: gets remote instance client
"""
return self._get_remote_instance_client()
def update_server_persona(self, clear_errors=True):
"""
@summary: updates the self.server entity doing a GET server call
@ -306,6 +354,49 @@ class ServerPersona(BaseModel, NetworkingBaseBehaviors):
if clear_errors:
self.errors = []
def add_security_groups_to_ports(self, port_type, security_group_ids,
port_ids=None, raise_exception=False):
"""
Adds a security group or groups to a server port or ports based on type
@param port_type: port network type, for ex. pnet (public),
snet (service), or inet (isolated).
@type port_type: str
@param security_group_ids: the escurity group or groups to
be added to the port(s).
@type security_group_ids: list
@param port_ids (optional): to target specific ports by ID where the
security group(s) should be added. They must match the network type
param. By default, all the server port(s) by the given network type
will be added the security group(s).
@type port_ids: list
"""
# Getting all the port ids from the network port type
port_ids_label = '{0}_port_ids'.format(port_type)
persona_port_ids = getattr(self, port_ids_label, None)
# Targeting specific ports if port_ids given
if port_ids:
port_ids_set = set(port_ids)
port_ids = list(port_ids_set.intersection(persona_port_ids))
else:
port_ids = persona_port_ids
# Adding the security group
for port_id in port_ids:
update = self.ports.behaviors.update_port(
port_id=port_id, security_groups=security_group_ids)
if update.failures:
msg = self.update_port_failure_msg.format(self.server.id,
port_type,
update.failures)
self.errors.append(msg)
self._log.error(msg)
if raise_exception:
raise PortUpdateException(msg)
return False
return True
def _port_response(self, network_type):
"""
@summary: returns server network ports based on network type
@ -327,6 +418,17 @@ class ServerPersona(BaseModel, NetworkingBaseBehaviors):
return []
return ports.response.entity
def _get_remote_instance_client(self):
"""
@summary: returns a remote instance client
"""
ip_address = self.pnet_fix_ipv4[0]
remote_client = self.behaviors.get_remote_instance_client(
server=self.server, ip_address=ip_address,
private_key=self.keypair.private_key,
ssh_username=self.ssh_username)
return remote_client
def _get_fixed_ips(self, ip_version, port_type, network_type):
"""
@summary: gets fixed IPs from server ports
@ -370,3 +472,16 @@ class ServerPersona(BaseModel, NetworkingBaseBehaviors):
ports = getattr(self, port_attr, [])
port_ids = [port.id for port in ports]
return port_ids
def _get_port_security_groups(self, port_type):
"""
@summary: gets the ports security groups
@param port_type: pnet, snet or inet port type
@type port_type: str
@return: all the security groups of the same port type
@rtype: list(list)
"""
port_attr = '{0}_ports'.format(port_type.lower())
ports = getattr(self, port_attr, [])
port_sec_groups = [port.security_groups for port in ports]
return port_sec_groups