~gz/pyjuju/0.5_unicode_token_backport

« back to all changes in this revision

Viewing changes to juju/providers/openstack/ports.py

  • Committer: Clint Byrum
  • Author(s): Martin Packman
  • Date: 2012-09-10 08:32:41 UTC
  • Revision ID: clint@ubuntu.com-20120910083241-xueuh0bt5jl44w2b
OpenStack Provider

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""Manage port access to machines using Nova security group extension
 
2
 
 
3
The mechanism is based on the existing scheme used by the EC2 provider.
 
4
 
 
5
Each machine is launched with two security groups, a juju group that is shared
 
6
across all machines and allows access to 22/tcp for ssh, and a machine group
 
7
just for that server so ports can be opened and closed on an individual level.
 
8
 
 
9
There is some mismatch between the port hole poking and security group models:
 
10
* A new security group is created for every machine
 
11
* Rules are not shared between service units but set up again each launch
 
12
* Support for port ranges is not exposed
 
13
 
 
14
The Nova security group module follows the EC2 example quite closely, but as
 
15
of Essex it's still under contrib and has a number of quirks:
 
16
* To run a server with, or add or remove groups from a server, 'name' is used
 
17
* To get details, delete, or add or remove rules from a group, 'id' is needed
 
18
 
 
19
The only way of getting 'id' if 'name' is known is by listing all groups then
 
20
looking at the details of the one with the matching name.
 
21
"""
 
22
 
 
23
from twisted.internet import (
 
24
    defer,
 
25
    )
 
26
 
 
27
from juju import errors
 
28
 
 
29
from .client import log
 
30
 
 
31
 
 
32
class NovaPortManager(object):
 
33
    """Mapping of port-based juju interface to Nova security group actions
 
34
 
 
35
    There is the potential to record some state on the instance to reduce api
 
36
    round-trips when, for instance, launching multiple machines at once, but
 
37
    for now
 
38
    """
 
39
 
 
40
    def __init__(self, nova, environment_name):
 
41
        self.nova = nova
 
42
        self.tag = environment_name
 
43
 
 
44
    def _juju_group_name(self):
 
45
        return "juju-%s" % (self.tag,)
 
46
 
 
47
    def _machine_group_name(self, machine_id):
 
48
        return "juju-%s-%s" % (self.tag, machine_id)
 
49
 
 
50
    @defer.inlineCallbacks
 
51
    def _get_machine_group(self, machine, machine_id):
 
52
        """Get details of the machine specific security group
 
53
 
 
54
        As only the name of the group can be derived, this means listing every
 
55
        security group for that server and seeing which has a matching name.
 
56
        """
 
57
        group_name = self._machine_group_name(machine_id)
 
58
        server_id = machine.instance_id
 
59
        groups = yield self.nova.get_server_security_groups(server_id)
 
60
        for group in groups:
 
61
            if group['name'] == group_name:
 
62
                defer.returnValue(group)
 
63
        raise errors.ProviderInteractionError(
 
64
            "Missing security group %r for machine %r" %
 
65
                (group_name, server_id))
 
66
 
 
67
    @defer.inlineCallbacks
 
68
    def open_port(self, machine, machine_id, port, protocol="tcp"):
 
69
        """Allow access to a port for the given machine only"""
 
70
        group = yield self._get_machine_group(machine, machine_id)
 
71
        yield self.nova.add_security_group_rule(group['id'],
 
72
            ip_protocol=protocol, from_port=port, to_port=port)
 
73
        log.debug("Opened %s/%s on machine %r",
 
74
            port, protocol, machine.instance_id)
 
75
 
 
76
    @defer.inlineCallbacks
 
77
    def close_port(self, machine, machine_id, port, protocol="tcp"):
 
78
        """Revoke access to a port for the given machine only"""
 
79
        group = yield self._get_machine_group(machine, machine_id)
 
80
        for rule in group["rules"]:
 
81
            if (port == rule["from_port"] == rule["to_port"] and
 
82
                rule["ip_protocol"] == protocol):
 
83
                yield self.nova.delete_security_group_rule(rule["id"])
 
84
                log.debug("Closed %s/%s on machine %r",
 
85
                          port, protocol, machine.instance_id)
 
86
                return
 
87
        raise errors.ProviderInteractionError(
 
88
            "Couldn't close unopened %s/%s on machine %r",
 
89
                  port, protocol, machine.instance_id)
 
90
 
 
91
    @defer.inlineCallbacks
 
92
    def get_opened_ports(self, machine, machine_id):
 
93
        """Get a set of opened port/protocol pairs for a machine"""
 
94
        group = yield self._get_machine_group(machine, machine_id)
 
95
        opened_ports = set()
 
96
        for rule in group.get("rules", []):
 
97
            if not rule.get("group"):
 
98
                protocol = rule["ip_protocol"]
 
99
                from_port = rule["from_port"]
 
100
                to_port = rule["to_port"]
 
101
                if from_port == to_port:
 
102
                    opened_ports.add((from_port, protocol))
 
103
        defer.returnValue(opened_ports)
 
104
 
 
105
    @defer.inlineCallbacks
 
106
    def ensure_groups(self, machine_id):
 
107
        """Get names of the security groups for a machine, creating if needed
 
108
 
 
109
        If the juju group already exists, it is assumed to be correctly set up.
 
110
        If the machine group already exists, it is deleted then recreated.
 
111
        """
 
112
        security_groups = yield self.nova.list_security_groups()
 
113
        groups_by_name = dict((sg['name'], sg['id']) for sg in security_groups)
 
114
 
 
115
        juju_group = self._juju_group_name()
 
116
        if not juju_group in groups_by_name:
 
117
            log.debug("Creating juju security group %s", juju_group)
 
118
            sg = yield self.nova.create_security_group(juju_group,
 
119
                "juju group for %s" % (self.tag,))
 
120
            # Add external ssh access
 
121
            yield self.nova.add_security_group_rule(sg['id'],
 
122
                ip_protocol="tcp", from_port=22, to_port=22)
 
123
            # Add internal group access
 
124
            yield self.nova.add_security_group_rule(
 
125
                parent_group_id=sg['id'], group_id=sg['id'],
 
126
                ip_protocol="tcp", from_port=1, to_port=65535)
 
127
 
 
128
        machine_group = self._machine_group_name(machine_id)
 
129
        if machine_group in groups_by_name:
 
130
            yield self.nova.delete_security_group(
 
131
                groups_by_name[machine_group])
 
132
        log.debug("Creating machine security group %s", machine_group)
 
133
        yield self.nova.create_security_group(machine_group,
 
134
            "juju group for %s machine %s" % (self.tag, machine_id))
 
135
 
 
136
        defer.returnValue([juju_group, machine_group])
 
137
 
 
138
    @defer.inlineCallbacks
 
139
    def get_machine_groups(self, machine, with_juju_group=False):
 
140
        try:
 
141
            ret = yield self.get_machine_groups_pure(machine, with_juju_group)
 
142
        except errors.ProviderInteractionError, e:
 
143
            # XXX: Need to wire up treatment of 500s properly in client
 
144
            if getattr(e, "kind", None) == "computeError":
 
145
                try:
 
146
                    yield self.nova.get_server(machine.instance_id)
 
147
                except errors.ProviderInteractionError, e:
 
148
                    pass  # just rebinding e
 
149
            if True or getattr(e, "kind", None) == "itemNotFound":
 
150
                defer.returnValue(None)
 
151
            raise
 
152
        defer.returnValue(ret)
 
153
 
 
154
    @defer.inlineCallbacks
 
155
    def get_machine_groups_pure(self, machine, with_juju_group=False):
 
156
        server_id = machine.instance_id
 
157
        groups = yield self.nova.get_server_security_groups(server_id)
 
158
        juju_group = self._juju_group_name()
 
159
        groups_by_name = dict((g['name'], g['id']) for g in groups
 
160
            if g['name'].startswith(juju_group))
 
161
        if juju_group not in groups_by_name:
 
162
            # Not a juju machine, shouldn't touch
 
163
            defer.returnValue(None)
 
164
        if not with_juju_group:
 
165
            groups_by_name.pop(juju_group)
 
166
        # else assumption: only one remaining group, is the machine group
 
167
        defer.returnValue(groups_by_name)
 
168
 
 
169
    @defer.inlineCallbacks
 
170
    def delete_juju_group(self):
 
171
        security_groups = yield self.nova.list_security_groups()
 
172
        juju_group = self._juju_group_name()
 
173
        for group in security_groups:
 
174
            if group['name'] == juju_group:
 
175
                break
 
176
        else:
 
177
            log.debug("Can't delete missing juju group")
 
178
            return
 
179
        yield self.nova.delete_security_group(group['id'])