~gz/pyjuju/0.5_unicode_token_backport

« back to all changes in this revision

Viewing changes to juju/providers/openstack/provider.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
"""Provider interface implementation for OpenStack backend
 
2
 
 
3
Much of the logic is implemented in sibling modules, but the overall model is
 
4
exposed here.
 
5
 
 
6
Still in need of work here:
 
7
* Implement constraints using the Nova flavors api. This will always mean an
 
8
  api call rather than hard coding values as is done with EC2. Things like
 
9
  memory and cpu count are broadly equivalent, but there's no guarentee what
 
10
  details are exposed and ranking by price will generally not be an option.
 
11
"""
 
12
 
 
13
import logging
 
14
 
 
15
from twisted.internet import defer
 
16
 
 
17
from juju import errors
 
18
from juju.providers.common.base import MachineProviderBase
 
19
 
 
20
from .client import _OpenStackClient, _NovaClient, _SwiftClient
 
21
from . import credentials
 
22
from .files import FileStorage
 
23
from .launch import NovaLaunchMachine
 
24
from .machine import (
 
25
    NovaProviderMachine, get_server_status, machine_from_instance
 
26
    )
 
27
from .ports import NovaPortManager
 
28
 
 
29
 
 
30
log = logging.getLogger("juju.openstack")
 
31
 
 
32
 
 
33
class MachineProvider(MachineProviderBase):
 
34
    """MachineProvider for use in an OpenStack environment"""
 
35
 
 
36
    Credentials = credentials.OpenStackCredentials
 
37
 
 
38
    def __init__(self, environment_name, config):
 
39
        super(MachineProvider, self).__init__(environment_name, config)
 
40
        self.credentials = self.Credentials.from_environment(config)
 
41
        client = _OpenStackClient(self.credentials)
 
42
        self.nova = _NovaClient(client)
 
43
        self.swift = _SwiftClient(client)
 
44
        self.port_manager = NovaPortManager(self.nova, environment_name)
 
45
 
 
46
    @property
 
47
    def provider_type(self):
 
48
        return "openstack"
 
49
 
 
50
    def get_serialization_data(self):
 
51
        """Get provider configuration suitable for serialization.
 
52
 
 
53
        Also fills in credential information that may have earlier been
 
54
        extracted from the environment.
 
55
        """
 
56
        data = super(MachineProvider, self).get_serialization_data()
 
57
        self.credentials.set_config_defaults(data)
 
58
        return data
 
59
 
 
60
    def get_file_storage(self):
 
61
        """Retrieve a Swift-backed :class:`FileStorage`."""
 
62
        return FileStorage(self.swift, self.config["control-bucket"])
 
63
 
 
64
    def start_machine(self, machine_data, master=False):
 
65
        """Start an OpenStack machine.
 
66
 
 
67
        :param dict machine_data: desired characteristics of the new machine;
 
68
            it must include a "machine-id" key, and may include a "constraints"
 
69
            key to specify the underlying OS and hardware.
 
70
 
 
71
        :param bool master: if True, machine will initialize the juju admin
 
72
            and run a provisioning agent, in addition to running a machine
 
73
            agent.
 
74
        """
 
75
        return NovaLaunchMachine.launch(self, machine_data, master)
 
76
 
 
77
    @defer.inlineCallbacks
 
78
    def get_machines(self, instance_ids=()):
 
79
        """List machines running in the provider.
 
80
 
 
81
        :param list instance_ids: ids of instances you want to get. Leave empty
 
82
            to list every
 
83
            :class:`juju.providers.openstack.machine.NovaProviderMachine` owned
 
84
            by this provider.
 
85
 
 
86
        :return: a list of
 
87
            :class:`juju.providers.openstack.machine.NovaProviderMachine`
 
88
            instances
 
89
        :rtype: :class:`twisted.internet.defer.Deferred`
 
90
 
 
91
        :raises: :exc:`juju.errors.MachinesNotFound`
 
92
        """
 
93
        if len(instance_ids) == 1:
 
94
            try:
 
95
                instances = [(yield self.nova.get_server(instance_ids[0]))]
 
96
            except errors.ProviderInteractionError, e:
 
97
                # XXX: Need to wire up treatment of 404s properly in client
 
98
                if True or getattr(e, "kind", None) == "itemNotFound":
 
99
                    raise errors.MachinesNotFound(set(instance_ids))
 
100
                raise
 
101
            instance_ids = frozenset(instance_ids)
 
102
        else:
 
103
            instances = yield self.nova.list_servers()
 
104
            if instance_ids:
 
105
                instance_ids = frozenset(instance_ids)
 
106
                instances = [instance for instance in instances
 
107
                    if instance['id'] in instance_ids]
 
108
 
 
109
        # Only want to deal with servers that were created by juju, checking
 
110
        # the name begins with the prefix launch uses is good enough.
 
111
        name_prefix = "juju %s instance " % (self.environment_name,)
 
112
        machines = []
 
113
        for instance in instances:
 
114
            if (instance['name'].startswith(name_prefix) and
 
115
                    get_server_status(instance) in ("running", "pending")):
 
116
                machines.append(machine_from_instance(instance))
 
117
 
 
118
        if instance_ids:
 
119
            # We were asked for a specific list of machines, and if we can't
 
120
            # completely fulfil that request we should blow up.
 
121
            missing = instance_ids.difference(m.instance_id for m in machines)
 
122
            if missing:
 
123
                raise errors.MachinesNotFound(missing)
 
124
 
 
125
        defer.returnValue(machines)
 
126
 
 
127
    @defer.inlineCallbacks
 
128
    def _delete_machine(self, machine, full=False):
 
129
        server_id = machine.instance_id
 
130
        server = yield self.nova.get_server(server_id)
 
131
        if not server['name'].startswith(
 
132
            "juju %s instance" % self.environment_name):
 
133
            raise errors.MachinesNotFound(set([machine.instance_id]))
 
134
        yield self.nova.delete_server(server_id)
 
135
        defer.returnValue(machine)
 
136
 
 
137
    def shutdown_machine(self, machine):
 
138
        if not isinstance(machine, NovaProviderMachine):
 
139
            raise errors.ProviderError(
 
140
                "Need a NovaProviderMachine to shutdown not: %r" % (machine,))
 
141
        # EC2 provider re-gets the machine to see if it's still in existance
 
142
        # and can be shutdown, instead just handle an error? 404-ish?
 
143
        return self._delete_machine(machine)
 
144
 
 
145
    @defer.inlineCallbacks
 
146
    def destroy_environment(self):
 
147
        """Terminate all associated machines and security groups.
 
148
 
 
149
        The super defintion of this method terminates each machine in
 
150
        the environment; this needs to be augmented here by also
 
151
        removing the security group for the environment.
 
152
 
 
153
        :rtype: :class:`twisted.internet.defer.Deferred`
 
154
        """
 
155
        machines = yield self.get_machines()
 
156
        deleted_machines = yield defer.gatherResults(
 
157
            [self._delete_machine(m, True) for m in machines])
 
158
        yield self.save_state({})
 
159
        defer.returnValue(deleted_machines)
 
160
 
 
161
    def shutdown_machines(self, machines):
 
162
        """Terminate machines associated with this provider.
 
163
 
 
164
        :param machines: machines to shut down
 
165
        :type machines: list of
 
166
            :class:`juju.providers.openstack.machine.NovaProviderMachine`
 
167
 
 
168
        :return: list of terminated
 
169
            :class:`juju.providers.openstack.machine.NovaProviderMachine`
 
170
            instances
 
171
        :rtype: :class:`twisted.internet.defer.Deferred`
 
172
        """
 
173
        # XXX: need to actually handle errors as non-terminated machines
 
174
        # and not include them in the resulting list
 
175
        return defer.gatherResults(
 
176
            [self.shutdown_machine(m) for m in machines], consumeErrors=True)
 
177
 
 
178
    def open_port(self, machine, machine_id, port, protocol="tcp"):
 
179
        """Authorizes `port` using `protocol` on EC2 for `machine`."""
 
180
        return self.port_manager.open_port(machine, machine_id, port, protocol)
 
181
 
 
182
    def close_port(self, machine, machine_id, port, protocol="tcp"):
 
183
        """Revokes `port` using `protocol` on EC2 for `machine`."""
 
184
        return self.port_manager.close_port(
 
185
            machine, machine_id, port, protocol)
 
186
 
 
187
    def get_opened_ports(self, machine, machine_id):
 
188
        """Returns a set of open (port, proto) pairs for `machine`."""
 
189
        return self.port_manager.get_opened_ports(machine, machine_id)