slapconfiguration.py 7.55 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
##############################################################################
#
# Copyright (c) 2012 Vifib SARL and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################
27 28 29 30

import json
import os

31
import slapos.slap
32
from slapos.recipe.librecipe import unwrap
33
from ConfigParser import RawConfigParser
34
from netaddr import valid_ipv4, valid_ipv6
35 36 37 38 39 40 41

class Recipe(object):
  """
  Retrieves slap partition parameters, and makes them available to other
  buildout section in various ways, and in various encodings.
  Populates the buildout section it is used in with all slap partition
  parameters.
42 43
  Also provides access to partition properties: all IPv4, IPv6 and tap
  interfaces it is allowed to use.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65

  Input:
    url
      Slap server url.
      Example:
        ${slap-connection:server-url}
    key & cert (optional)
      Path of files containing key and certificate for secure connection to
      slap server.
      Example:
        ${slap-connection:key-file}
        ${slap-connection:cert-file}
    computer
      Computer identifier.
      Example:
        ${slap-connection:computer-id}
    partition
      Partition identifier.
      Example:
        ${slap-connection:partition-id}

  Output:
66 67
    slap-software-type
      Current partition's software type.
68 69 70 71
    ipv4
      Set of IPv4 addresses.
    ipv6
      Set of IPv6 addresses.
72
    ipv4-random
73
      One of the IPv4 addresses.
74
    ipv6-random
75
      One of the IPv6 addresses.
76 77
    tap
      Set of TAP interfaces.
78 79 80 81 82 83 84
    configuration
      Dict of all parameters.
    configuration.<key>
      One key per partition parameter.
      Partition parameter whose name cannot be represented unambiguously in
      buildout syntax are ignored. They cannot be accessed from buildout syntax
      anyway, and are available through "configuration" output key.
85 86
    instance-state
      The instance state.
87 88 89 90
  """

  # XXX: used to detect if a configuration key is a valid section key. This
  # assumes buildout uses ConfigParser - which is currently the case.
91
  OPTCRE_match = RawConfigParser.OPTCRE.match
92 93

  def __init__(self, buildout, name, options):
94 95 96 97 98 99 100 101 102
      parameter_dict = self.fetch_parameter_dict(options)

      match = self.OPTCRE_match
      for key, value in parameter_dict.iteritems():
          if match(key) is not None:
              continue
          options['configuration.' + key] = value

  def fetch_parameter_dict(self, options):
103 104
      slap = slapos.slap.slap()
      slap.initializeConnection(
Vincent Pelletier's avatar
Vincent Pelletier committed
105 106 107
          options['url'],
          options.get('key'),
          options.get('cert'),
108
      )
109
      computer_partition = slap.registerComputerPartition(
Vincent Pelletier's avatar
Vincent Pelletier committed
110 111
          options['computer'],
          options['partition'],
112 113 114
      )
      parameter_dict = computer_partition.getInstanceParameterDict()
      options['instance-state'] = computer_partition.getState()
115
      # XXX: those are not partition parameters, strictly speaking.
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
      # Make them available as individual section keys.
      for his_key in (
                  'slap_software_type',
                  'slap_computer_partition_id',
                  'slap_computer_id',
                  'slap_software_release_url',
                  'slave_instance_list',
                  'timestamp',
              ):
          try:
              value = parameter_dict.pop(his_key)
          except KeyError:
              pass
          else:
              options[his_key.replace('_', '-')] = value
131 132 133 134 135 136
      ipv4_set = set()
      v4_add = ipv4_set.add
      ipv6_set = set()
      v6_add = ipv6_set.add
      tap_set = set()
      tap_add = tap_set.add
137 138 139 140 141 142 143 144
      route_gw_set = set()
      route_gw_add = route_gw_set.add
      route_mask_set = set()
      route_mask_add = route_mask_set.add
      route_ipv4_set = set()
      route_v4_add = route_ipv4_set.add
      route_network_set = set()
      route_net_add = route_network_set.add
145 146 147 148 149 150 151
      for tap, ip in parameter_dict.pop('ip_list'):
          tap_add(tap)
          if valid_ipv4(ip):
              v4_add(ip)
          elif valid_ipv6(ip):
              v6_add(ip)
          # XXX: emit warning on unknown address type ?
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166

      if 'full_ip_list' in parameter_dict:
        for item in parameter_dict.pop('full_ip_list'):
          if len(item) == 5:
            tap, ip, gw, netmask, network = item
            if  tap.startswith('route_'):
              if valid_ipv4(gw):
                route_gw_add(gw)
              if valid_ipv4(netmask):
                route_mask_add(netmask)
              if valid_ipv4(ip):
                route_v4_add(ip)
              if valid_ipv4(network):
                route_net_add(network)

167 168
      options['ipv4'] = ipv4_set
      options['ipv6'] = ipv6_set
169 170 171

      # also export single ip values for those recipes that don't support sets.
      if ipv4_set:
172
          options['ipv4-random'] = list(ipv4_set)[0].encode('UTF-8')
173
      if ipv6_set:
174
          options['ipv6-random'] = list(ipv6_set)[0].encode('UTF-8')
175 176 177 178 179 180 181 182 183 184 185 186 187 188
      if route_ipv4_set:
        options['tap-ipv4'] = list(route_ipv4_set)[0].encode('UTF-8')
        options['tap-network-information-dict'] = dict(ipv4=route_ipv4_set,
                                    netmask=route_mask_set,
                                    gateway=route_gw_set,
                                    network=route_network_set)
      else:
        options['tap-network-information-dict'] = {}
      if route_gw_set:
        options['tap-gateway'] = list(route_gw_set)[0].encode('UTF-8')
      if route_mask_set:
        options['tap-netmask'] = list(route_mask_set)[0].encode('UTF-8')
      if route_network_set:
        options['tap-network'] = list(route_network_set)[0].encode('UTF-8')
189

190
      options['tap'] = tap_set
191
      return self._expandParameterDict(options, parameter_dict)
192

193 194 195 196
  def _expandParameterDict(self, options, parameter_dict):
      options['configuration'] = parameter_dict
      return parameter_dict

Vincent Pelletier's avatar
Vincent Pelletier committed
197
  install = update = lambda self: []
198 199 200 201 202 203 204 205

class Serialised(Recipe):
  def _expandParameterDict(self, options, parameter_dict):
      options['configuration'] = parameter_dict = unwrap(parameter_dict)
      if isinstance(parameter_dict, dict):
          return parameter_dict
      else:
          return {}
206 207 208 209

class JsonDump(Recipe):
  def __init__(self, buildout, name, options):
    parameter_dict = self.fetch_parameter_dict(options)
210 211
    self._json_output = options['json-output']
    with os.fdopen(os.open(self._json_output, os.O_WRONLY | os.O_CREAT, 0600), 'w') as fout:
212 213
      fout.write(json.dumps(parameter_dict, indent=2, sort_keys=True))

214 215 216 217 218
    def install(self):
        return [self._json_output]

    update = install