client.py 11 KB
Newer Older
1
# -*- coding: utf-8 -*-
Łukasz Nowak's avatar
Łukasz Nowak committed
2 3
##############################################################################
#
4 5
# Copyright (c) 2010, 2011, 2012 Vifib SARL and Contributors.
# All Rights Reserved.
Łukasz Nowak's avatar
Łukasz Nowak committed
6 7 8 9 10
#
# 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
11
# guarantees and support are strongly advised to contract a Free Software
Łukasz Nowak's avatar
Łukasz Nowak committed
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
# 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.
#
##############################################################################

30
import argparse
31 32 33 34
import ConfigParser
import pprint
from optparse import OptionParser, Option
import os
35
from slapos.slap import ResourceNotReady
36
import slapos.slap.slap
37
import sys
38
import atexit
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66

class Parser(OptionParser):
  """
  Parse all arguments.
  """
  def __init__(self, usage=None, version=None):
    """
    Initialize all options possibles.
    """
    OptionParser.__init__(self, usage=usage, version=version,
                          option_list=[
        Option("-u", "--master_url",
               default=None,
               action="store",
               help="Url of SlapOS Master to use."),
        Option("-k", "--key_file",
              action="store",
              help="SSL Authorisation key file."),
        Option("-c", "--cert_file",
            action="store",
            help="SSL Authorisation certificate file.")
    ])

  def check_args(self):
    """
    Check arguments
    """
    (options, args) = self.parse_args()
67 68
    if len(args) == 0:
      self.error("Incorrect number of arguments")
69 70
    elif not os.path.isfile(args[0]):
      self.error("%s: Not found or not a regular file." % args[0])
71

72 73
    # Return options and only first element of args since there is only one.
    return options, args[0]
74

75

76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
def argToDict(element):
  """
  convert a table of string 'key=value' to dict
  """
  if element is not None:
    element_dict = dict([arg.split('=') for arg in element])
  return element_dict

def check_request_args():
  """
  Parser for request
  """
  parser = argparse.ArgumentParser()
  parser.add_argument("configuration_file",
                      help="SlapOS configuration file.")
  parser.add_argument("reference",
                      help="Your instance reference")
  parser.add_argument("software_url",
                      help="Your software url")
  parser.add_argument("--node",
                      nargs = '*',
                      help = "Node request option "
                      "'option1=value1 option2=value2'")
  parser.add_argument("--type",
                      type = str,
                      help = "Define software type to be requested")
  parser.add_argument("--slave",
                      action = "store_true", default=False,
                      help = "Ask for a slave instance")
  parser.add_argument("--configuration",
                      nargs = '*',
                      help = "Give your configuration "
                      "'option1=value1 option2=value2'")
  args = parser.parse_args()
  # Convert to dict
  if args.configuration is not None:
    args.configuration = argToDict(args.configuration)
  if args.node is not None:
    args.node = argToDict(args.node)
  return args

117

118
class Config:
Cédric de Saint Martin's avatar
Cédric de Saint Martin committed
119
  def __init__(self, option_dict, configuration_file_path=None):
120 121 122 123 124 125 126 127 128
    """
    Set options given by parameters.
    """
    # Set options parameters
    for option, value in option_dict.__dict__.items():
      setattr(self, option, value)

    # Load configuration file
    configuration_parser = ConfigParser.SafeConfigParser()
129 130 131 132 133 134
    if configuration_file_path:
      configuration_file_path = os.path.expanduser(configuration_file_path)
      if not os.path.isfile(configuration_file_path):
        raise OSError('Specified configuration file %s does not exist.'
            ' Exiting.' % configuration_file_path)
      configuration_parser.read(configuration_file_path)
135
    # Merges the arguments and configuration
136
    try:
137
      configuration_dict = dict(configuration_parser.items('slapconsole'))
138 139 140 141 142
    except ConfigParser.NoSectionError:
      pass
    else:
      for key in configuration_dict:
        if not getattr(self, key, None):
143
          setattr(self, key, configuration_dict[key])
144 145
    configuration_dict = dict(configuration_parser.items('slapos'))
    master_url = configuration_dict.get('master_url', None)
146 147 148 149 150 151
    # Backward compatibility, if no key and certificate given in option
    # take one from slapos configuration
    if not getattr(self, 'key_file', None) and \
          not getattr(self, 'cert_file', None):
      self.key_file = configuration_dict.get('key_file')
      self.cert_file = configuration_dict.get('cert_file')
152 153 154
    if not master_url:
      raise ValueError("No option 'master_url'")
    elif master_url.startswith('https') and \
155 156 157
         self.key_file is None and \
         self.cert_file is None:
        raise ValueError("No option 'key_file' and/or 'cert_file'")
158 159
    else:
      setattr(self, 'master_url', master_url)
160 161

def init(config):
162
  """Initialize Slap instance, connect to server and create
163
  aliases to common software releases"""
164
  slap = slapos.slap.slap()
165
  slap.initializeConnection(config.master_url,
166
      key_file=config.key_file, cert_file=config.cert_file)
167
  local = globals().copy()
168
  local['slap'] = slap
169
  # Create aliases as global variables
170 171 172 173
  try:
    alias = config.alias.split('\n')
  except AttributeError:
    alias = []
174
  software_list = []
175 176 177
  for software in alias:
    if software is not '':
      name, url = software.split(' ')
178
      software_list.append(name)
179
      local[name] = url
180
  # Create global variable too see available aliases
181
  local['software_list'] = software_list
182
  # Create global shortcut functions to request instance and software
183 184 185 186
  def shorthandRequest(*args, **kwargs):
    return slap.registerOpenOrder().request(*args, **kwargs)
  def shorthandSupply(*args, **kwargs):
    return slap.registerSupply().supply(*args, **kwargs)
187 188 189
  local['request'] = shorthandRequest
  local['supply'] = shorthandSupply

190 191 192
  return local

def request():
Cédric de Saint Martin's avatar
Cédric de Saint Martin committed
193
  """Run when invoking slapos request. Request an instance."""
194
  # Parse arguments and inititate needed parameters
195
  # XXX-Cedric: move argument parsing to main entry point
196
  options = check_request_args()
Cédric de Saint Martin's avatar
Cédric de Saint Martin committed
197
  config = Config(options, options.configuration_file)
198 199
  local = init(config)
  # Request instance
200
  print("Requesting %s..." % config.reference)
201 202
  if config.software_url in local:
    config.software_url = local[config.software_url]
203
  try:
204 205 206 207 208 209 210
    partition = local['slap'].registerOpenOrder().request(
      software_release = config.software_url,
      partition_reference = config.reference,
      partition_parameter_kw = config.configuration,
      software_type = config.type,
      filter_kw = config.node,
      shared = config.slave
211
    )
212 213 214 215
    print "Instance requested.\nState is : %s." % partition.getState()
    print "Connection parameters of instance are:"
    pprint.pprint(partition.getConnectionParameterDict())
    print "You can rerun command to get up-to-date informations."
216 217
  except ResourceNotReady:
    print("Instance requested. Master is provisionning it. Please rerun in a "
218
        "couple of minutes to get connection informations.")
219
    exit(2)
220

221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
def _supply(software_url, computer_id, local, remove=False):
  """
  Request installation of Software Release
  'software_url' on computer 'computer_id'.
  if destroy argument is True, request deletion of Software Release.
  """
  # XXX-Cedric Implement software_group support
  # XXX-Cedric Implement computer_group support
  if not remove:
    state = 'available'
    print 'Requesting installation of %s Software Release...' % software_url

  else:
    state = 'destroyed'
    print 'Requesting deletion of %s Software Release...' % software_url

  if software_url in local:
    software_url = local[software_url]
Cédric de Saint Martin's avatar
Cédric de Saint Martin committed
239 240 241
  local['slap'].registerSupply().supply(
      software_release=software_url,
      computer_guid=computer_id,
242 243 244 245 246 247
      state=state,
  )
  print 'Done.'

def supply():
  """
Cédric de Saint Martin's avatar
Cédric de Saint Martin committed
248
  Run when invoking slapos supply. Mostly argument parsing.
249 250 251 252 253 254 255 256
  """
  # XXX-Cedric: move argument parsing to main entry point
  parser = argparse.ArgumentParser()
  parser.add_argument("configuration_file",
                      help="SlapOS configuration file")
  parser.add_argument("software_url",
                      help="Your software url")
  parser.add_argument("node",
Cédric de Saint Martin's avatar
Cédric de Saint Martin committed
257
                      help="Target node")
258 259
  args = parser.parse_args()

Cédric de Saint Martin's avatar
Cédric de Saint Martin committed
260 261
  config = Config(args, args.configuration_file)
  _supply(args.software_url, args.node, init(config))
262 263 264

def remove():
  """
Cédric de Saint Martin's avatar
Cédric de Saint Martin committed
265
  Run when invoking slapos remove. Mostly argument parsing.
266 267 268 269 270 271 272 273
  """
  # XXX-Cedric: move argument parsing to main entry point
  parser = argparse.ArgumentParser()
  parser.add_argument("configuration_file",
                      help="SlapOS configuration file.")
  parser.add_argument("software_url",
                      help="Your software url")
  parser.add_argument("node",
Cédric de Saint Martin's avatar
Cédric de Saint Martin committed
274
                      help="Target node")
275 276
  args = parser.parse_args()

Cédric de Saint Martin's avatar
Cédric de Saint Martin committed
277 278
  config = Config(args, args.configuration_file)
  _supply(args.software_url, args.node, init(config), remove=True)
279 280


281
def slapconsole():
282 283
  """Ran when invoking slapconsole"""
  # Parse arguments
284
  usage = """usage: %s [options] CONFIGURATION_FILE
285 286 287 288 289 290 291 292 293 294
slapconsole allows you interact with slap API. You can play with the global
"slap" object and with the global "request" method.

examples :
  >>> # Request instance
  >>> request(kvm, "myuniquekvm")
  >>> # Request software installation on owned computer
  >>> supply(kvm, "mycomputer")
  >>> # Fetch instance informations on already launched instance
  >>> request(kvm, "myuniquekvm").getConnectionParameter("url")""" % sys.argv[0]
Cédric de Saint Martin's avatar
Cédric de Saint Martin committed
295
  config = Config(*Parser(usage=usage).check_args())
296
  local = init(config)
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317

  # try to enable readline with completion and history
  try:
    import readline
  except ImportError:
    pass
  else:
    try:
      import rlcompleter
      readline.set_completer(rlcompleter.Completer(local).complete)
    except ImportError:
      pass
    readline.parse_and_bind("tab: complete")

    historyPath = os.path.expanduser("~/.slapconsolehistory")
    def save_history(historyPath=historyPath):
      readline.write_history_file(historyPath)
    if os.path.exists(historyPath):
      readline.read_history_file(historyPath)
    atexit.register(save_history)

318
  __import__("code").interact(banner="", local=local)
319