~gandelman-a/ubuntu/precise/nova/UCA_2012.2.1

« back to all changes in this revision

Viewing changes to nova/api/ec2/cloud.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short, Chuck Short, Adam Gandelman
  • Date: 2012-06-22 12:39:57 UTC
  • mfrom: (1.1.57)
  • Revision ID: package-import@ubuntu.com-20120622123957-hbzwg84nt9rqwg8r
Tags: 2012.2~f2~20120621.14517-0ubuntu1
[ Chuck Short ]
* New upstream version.

[ Adam Gandelman ]
* debian/rules: Temporarily disable test suite while blocking
  tests are investigated. 
* debian/patches/kombu_tests_timeout.patch: Dropped.

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
import base64
26
26
import re
27
27
import time
28
 
import urllib
29
28
 
30
29
from nova.api.ec2 import ec2utils
31
30
from nova.api.ec2 import inst_state
41
40
from nova import log as logging
42
41
from nova import network
43
42
from nova.openstack.common import excutils
44
 
from nova.openstack.common import importutils
 
43
from nova.openstack.common import timeutils
45
44
from nova import quota
46
45
from nova import utils
47
46
from nova import volume
190
189
        self.image_service = s3.S3ImageService()
191
190
        self.network_api = network.API()
192
191
        self.volume_api = volume.API()
 
192
        self.security_group_api = CloudSecurityGroupAPI()
193
193
        self.compute_api = compute.API(network_api=self.network_api,
194
 
                                       volume_api=self.volume_api)
 
194
                                   volume_api=self.volume_api,
 
195
                                   security_group_api=self.security_group_api)
195
196
        self.keypair_api = compute.api.KeypairAPI()
196
 
        self.sgh = importutils.import_object(FLAGS.security_group_handler)
197
197
 
198
198
    def __str__(self):
199
199
        return 'CloudController'
337
337
        if not key_name is None:
338
338
            key_pairs = [x for x in key_pairs if x['name'] in key_name]
339
339
 
 
340
        #If looking for non existent key pair
 
341
        if key_name != None and key_pairs == []:
 
342
            msg = _('Could not find key pair(s): %s') % ','.join(key_name)
 
343
            raise exception.EC2APIError(msg)
 
344
 
340
345
        result = []
341
346
        for key_pair in key_pairs:
342
347
            # filter out the vpn keys
406
411
 
407
412
    def describe_security_groups(self, context, group_name=None, group_id=None,
408
413
                                 **kwargs):
409
 
        self.compute_api.ensure_default_security_group(context)
410
 
        if group_name or group_id:
411
 
            groups = []
412
 
            if group_name:
413
 
                for name in group_name:
414
 
                    group = db.security_group_get_by_name(context,
415
 
                                                          context.project_id,
416
 
                                                          name)
417
 
                    groups.append(group)
418
 
            if group_id:
419
 
                for gid in group_id:
420
 
                    group = db.security_group_get(context, gid)
421
 
                    groups.append(group)
422
 
        elif context.is_admin:
423
 
            groups = db.security_group_get_all(context)
424
 
        else:
425
 
            groups = db.security_group_get_by_project(context,
426
 
                                                      context.project_id)
427
 
        groups = [self._format_security_group(context, g) for g in groups]
 
414
        raw_groups = self.security_group_api.list(context,
 
415
                                                  group_name,
 
416
                                                  group_id,
 
417
                                                  context.project_id)
 
418
 
 
419
        groups = [self._format_security_group(context, g) for g in raw_groups]
428
420
 
429
421
        return {'securityGroupInfo':
430
422
                list(sorted(groups,
531
523
            notfound = exception.SecurityGroupNotFound
532
524
            if not source_security_group:
533
525
                raise notfound(security_group_id=source_security_group_name)
534
 
            values['group_id'] = source_security_group['id']
535
 
        elif cidr_ip:
536
 
            # If this fails, it throws an exception. This is what we want.
537
 
            cidr_ip = urllib.unquote(cidr_ip).decode()
538
 
 
539
 
            if not utils.is_valid_cidr(cidr_ip):
540
 
                # Raise exception for non-valid address
541
 
                raise exception.EC2APIError(_("Invalid CIDR"))
542
 
 
543
 
            values['cidr'] = cidr_ip
544
 
        else:
545
 
            values['cidr'] = '0.0.0.0/0'
546
 
 
547
 
        if source_security_group_name:
548
 
            # Open everything if an explicit port range or type/code are not
549
 
            # specified, but only if a source group was specified.
550
 
            ip_proto_upper = ip_protocol.upper() if ip_protocol else ''
551
 
            if (ip_proto_upper == 'ICMP' and
552
 
                from_port is None and to_port is None):
553
 
                from_port = -1
554
 
                to_port = -1
555
 
            elif (ip_proto_upper in ['TCP', 'UDP'] and from_port is None
556
 
                  and to_port is None):
557
 
                from_port = 1
558
 
                to_port = 65535
559
 
 
560
 
        if ip_protocol and from_port is not None and to_port is not None:
561
 
 
562
 
            ip_protocol = str(ip_protocol)
563
 
            try:
564
 
                # Verify integer conversions
565
 
                from_port = int(from_port)
566
 
                to_port = int(to_port)
567
 
            except ValueError:
568
 
                if ip_protocol.upper() == 'ICMP':
569
 
                    raise exception.InvalidInput(reason="Type and"
570
 
                         " Code must be integers for ICMP protocol type")
571
 
                else:
572
 
                    raise exception.InvalidInput(reason="To and From ports "
573
 
                          "must be integers")
574
 
 
575
 
            if ip_protocol.upper() not in ['TCP', 'UDP', 'ICMP']:
576
 
                raise exception.InvalidIpProtocol(protocol=ip_protocol)
577
 
 
578
 
            # Verify that from_port must always be less than
579
 
            # or equal to to_port
580
 
            if (ip_protocol.upper() in ['TCP', 'UDP'] and
581
 
                (from_port > to_port)):
582
 
                raise exception.InvalidPortRange(from_port=from_port,
583
 
                      to_port=to_port, msg="Former value cannot"
584
 
                                            " be greater than the later")
585
 
 
586
 
            # Verify valid TCP, UDP port ranges
587
 
            if (ip_protocol.upper() in ['TCP', 'UDP'] and
588
 
                (from_port < 1 or to_port > 65535)):
589
 
                raise exception.InvalidPortRange(from_port=from_port,
590
 
                      to_port=to_port, msg="Valid TCP ports should"
591
 
                                           " be between 1-65535")
592
 
 
593
 
            # Verify ICMP type and code
594
 
            if (ip_protocol.upper() == "ICMP" and
595
 
                (from_port < -1 or from_port > 255 or
596
 
                to_port < -1 or to_port > 255)):
597
 
                raise exception.InvalidPortRange(from_port=from_port,
598
 
                      to_port=to_port, msg="For ICMP, the"
599
 
                                           " type:code must be valid")
600
 
 
601
 
            values['protocol'] = ip_protocol
602
 
            values['from_port'] = from_port
603
 
            values['to_port'] = to_port
604
 
        else:
605
 
            # If cidr based filtering, protocol and ports are mandatory
606
 
            if 'cidr' in values:
607
 
                return None
608
 
 
609
 
        return values
610
 
 
611
 
    def _security_group_rule_exists(self, security_group, values):
612
 
        """Indicates whether the specified rule values are already
613
 
           defined in the given security group.
614
 
        """
615
 
        for rule in security_group.rules:
616
 
            is_duplicate = True
617
 
            keys = ('group_id', 'cidr', 'from_port', 'to_port', 'protocol')
618
 
            for key in keys:
619
 
                if rule.get(key) != values.get(key):
620
 
                    is_duplicate = False
621
 
                    break
622
 
            if is_duplicate:
623
 
                return rule['id']
624
 
        return False
 
526
            group_id = source_security_group['id']
 
527
            return self.security_group_api.new_group_ingress_rule(
 
528
                                    group_id, ip_protocol, from_port, to_port)
 
529
        else:
 
530
            cidr = self.security_group_api.parse_cidr(cidr_ip)
 
531
            return self.security_group_api.new_cidr_ingress_rule(
 
532
                                        cidr, ip_protocol, from_port, to_port)
 
533
 
 
534
    def _validate_group_identifier(self, group_name, group_id):
 
535
        if not group_name and not group_id:
 
536
            err = _("Not enough parameters, need group_name or group_id")
 
537
            raise exception.EC2APIError(err)
 
538
 
 
539
    def _validate_rulevalues(self, rulesvalues):
 
540
        if not rulesvalues:
 
541
            err = _("%s Not enough parameters to build a valid rule")
 
542
            raise exception.EC2APIError(err % rulesvalues)
625
543
 
626
544
    def revoke_security_group_ingress(self, context, group_name=None,
627
545
                                      group_id=None, **kwargs):
628
 
        if not group_name and not group_id:
629
 
            err = _("Not enough parameters, need group_name or group_id")
630
 
            raise exception.EC2APIError(err)
631
 
        self.compute_api.ensure_default_security_group(context)
632
 
        notfound = exception.SecurityGroupNotFound
633
 
        if group_name:
634
 
            security_group = db.security_group_get_by_name(context,
635
 
                                                           context.project_id,
636
 
                                                           group_name)
637
 
            if not security_group:
638
 
                raise notfound(security_group_id=group_name)
639
 
        if group_id:
640
 
            security_group = db.security_group_get(context, group_id)
641
 
            if not security_group:
642
 
                raise notfound(security_group_id=group_id)
643
 
 
644
 
        msg = _("Revoke security group ingress %s")
645
 
        LOG.audit(msg, security_group['name'], context=context)
646
 
        prevalues = []
647
 
        try:
648
 
            prevalues = kwargs['ip_permissions']
649
 
        except KeyError:
650
 
            prevalues.append(kwargs)
651
 
        rule_id = None
 
546
        self._validate_group_identifier(group_name, group_id)
 
547
 
 
548
        security_group = self.security_group_api.get(context, group_name,
 
549
                                                     group_id)
 
550
 
 
551
        prevalues = kwargs.get('ip_permissions', [kwargs])
 
552
 
652
553
        rule_ids = []
653
554
        for values in prevalues:
654
555
            rulesvalues = self._rule_args_to_dict(context, values)
655
 
            if not rulesvalues:
656
 
                err = _("%s Not enough parameters to build a valid rule")
657
 
                raise exception.EC2APIError(err % rulesvalues)
658
 
 
 
556
            self._validate_rulevalues(rulesvalues)
659
557
            for values_for_rule in rulesvalues:
660
558
                values_for_rule['parent_group_id'] = security_group.id
661
 
                rule_id = self._security_group_rule_exists(security_group,
662
 
                                                           values_for_rule)
663
 
                if rule_id:
664
 
                    db.security_group_rule_destroy(context, rule_id)
665
 
                    rule_ids.append(rule_id)
666
 
        if rule_id:
667
 
            # NOTE(vish): we removed a rule, so refresh
668
 
            self.compute_api.trigger_security_group_rules_refresh(
669
 
                    context,
670
 
                    security_group_id=security_group['id'])
671
 
            self.sgh.trigger_security_group_rule_destroy_refresh(
672
 
                    context, rule_ids)
 
559
 
 
560
                rule_ids.append(self.security_group_api.rule_exists(
 
561
                                             security_group, values_for_rule))
 
562
 
 
563
        rule_ids = [id for id in rule_ids if id]
 
564
 
 
565
        if rule_ids:
 
566
            self.security_group_api.remove_rules(context, security_group,
 
567
                                                 rule_ids)
 
568
 
673
569
            return True
 
570
 
674
571
        raise exception.EC2APIError(_("No rule for the specified parameters."))
675
572
 
676
573
    # TODO(soren): This has only been tested with Boto as the client.
679
576
    #              is sketchy.
680
577
    def authorize_security_group_ingress(self, context, group_name=None,
681
578
                                         group_id=None, **kwargs):
682
 
        if not group_name and not group_id:
683
 
            err = _("Not enough parameters, need group_name or group_id")
684
 
            raise exception.EC2APIError(err)
685
 
        self.compute_api.ensure_default_security_group(context)
686
 
        notfound = exception.SecurityGroupNotFound
687
 
        if group_name:
688
 
            security_group = db.security_group_get_by_name(context,
689
 
                                                           context.project_id,
690
 
                                                           group_name)
691
 
            if not security_group:
692
 
                raise notfound(security_group_id=group_name)
693
 
        if group_id:
694
 
            security_group = db.security_group_get(context, group_id)
695
 
            if not security_group:
696
 
                raise notfound(security_group_id=group_id)
697
 
 
698
 
        msg = _("Authorize security group ingress %s")
699
 
        LOG.audit(msg, security_group['name'], context=context)
700
 
        prevalues = []
701
 
        try:
702
 
            prevalues = kwargs['ip_permissions']
703
 
        except KeyError:
704
 
            prevalues.append(kwargs)
 
579
        self._validate_group_identifier(group_name, group_id)
 
580
 
 
581
        security_group = self.security_group_api.get(context, group_name,
 
582
                                                     group_id)
 
583
 
 
584
        prevalues = kwargs.get('ip_permissions', [kwargs])
705
585
        postvalues = []
706
586
        for values in prevalues:
707
587
            rulesvalues = self._rule_args_to_dict(context, values)
708
 
            if not rulesvalues:
709
 
                err = _("%s Not enough parameters to build a valid rule")
710
 
                raise exception.EC2APIError(err % rulesvalues)
 
588
            self._validate_rulevalues(rulesvalues)
711
589
            for values_for_rule in rulesvalues:
712
590
                values_for_rule['parent_group_id'] = security_group.id
713
 
                if self._security_group_rule_exists(security_group,
714
 
                                                    values_for_rule):
 
591
                if self.security_group_api.rule_exists(security_group,
 
592
                                                       values_for_rule):
715
593
                    err = _('%s - This rule already exists in group')
716
594
                    raise exception.EC2APIError(err % values_for_rule)
717
595
                postvalues.append(values_for_rule)
718
596
 
719
 
        count = QUOTAS.count(context, 'security_group_rules',
720
 
                             security_group['id'])
721
 
        try:
722
 
            QUOTAS.limit_check(context, security_group_rules=count + 1)
723
 
        except exception.OverQuota:
724
 
            msg = _("Quota exceeded, too many security group rules.")
725
 
            raise exception.EC2APIError(msg)
726
 
 
727
 
        rule_ids = []
728
 
        for values_for_rule in postvalues:
729
 
            security_group_rule = db.security_group_rule_create(
730
 
                    context,
731
 
                    values_for_rule)
732
 
            rule_ids.append(security_group_rule['id'])
733
 
 
734
597
        if postvalues:
735
 
            self.compute_api.trigger_security_group_rules_refresh(
736
 
                    context,
737
 
                    security_group_id=security_group['id'])
738
 
            self.sgh.trigger_security_group_rule_create_refresh(
739
 
                    context, rule_ids)
 
598
            self.security_group_api.add_rules(context, security_group['id'],
 
599
                                           security_group['name'], postvalues)
740
600
            return True
741
601
 
742
602
        raise exception.EC2APIError(_("No rule for the specified parameters."))
759
619
        return source_project_id
760
620
 
761
621
    def create_security_group(self, context, group_name, group_description):
762
 
        if not re.match('^[a-zA-Z0-9_\- ]+$', str(group_name)):
763
 
            # Some validation to ensure that values match API spec.
764
 
            # - Alphanumeric characters, spaces, dashes, and underscores.
765
 
            # TODO(Daviey): LP: #813685 extend beyond group_name checking, and
766
 
            #  probably create a param validator that can be used elsewhere.
767
 
            err = _("Value (%s) for parameter GroupName is invalid."
768
 
                    " Content limited to Alphanumeric characters, "
769
 
                    "spaces, dashes, and underscores.") % group_name
770
 
            # err not that of master ec2 implementation, as they fail to raise.
771
 
            raise exception.InvalidParameterValue(err=err)
772
 
 
773
 
        if len(str(group_name)) > 255:
774
 
            err = _("Value (%s) for parameter GroupName is invalid."
775
 
                    " Length exceeds maximum of 255.") % group_name
776
 
            raise exception.InvalidParameterValue(err=err)
777
 
 
778
 
        LOG.audit(_("Create Security Group %s"), group_name, context=context)
779
 
        self.compute_api.ensure_default_security_group(context)
780
 
        if db.security_group_exists(context, context.project_id, group_name):
781
 
            msg = _('group %s already exists')
782
 
            raise exception.EC2APIError(msg % group_name)
783
 
 
784
 
        try:
785
 
            reservations = QUOTAS.reserve(context, security_groups=1)
786
 
        except exception.OverQuota:
787
 
            msg = _("Quota exceeded, too many security groups.")
788
 
            raise exception.EC2APIError(msg)
789
 
 
790
 
        try:
791
 
            group = {'user_id': context.user_id,
792
 
                     'project_id': context.project_id,
793
 
                     'name': group_name,
794
 
                     'description': group_description}
795
 
            group_ref = db.security_group_create(context, group)
796
 
 
797
 
            self.sgh.trigger_security_group_create_refresh(context, group)
798
 
 
799
 
            # Commit the reservation
800
 
            QUOTAS.commit(context, reservations)
801
 
        except Exception:
802
 
            with excutils.save_and_reraise_exception():
803
 
                QUOTAS.rollback(context, reservations)
 
622
        if isinstance(group_name, unicode):
 
623
            group_name = group_name.encode('utf-8')
 
624
        if FLAGS.ec2_strict_validation:
 
625
            # EC2 specification gives constraints for name and description:
 
626
            # Accepts alphanumeric characters, spaces, dashes, and underscores
 
627
            allowed = '^[a-zA-Z0-9_\- ]+$'
 
628
            self.security_group_api.validate_property(group_name, 'name',
 
629
                                                      allowed)
 
630
            self.security_group_api.validate_property(group_description,
 
631
                                                      'description', allowed)
 
632
        else:
 
633
            # Amazon accepts more symbols.
 
634
            # So, allow POSIX [:print:] characters.
 
635
            allowed = r'^[\x20-\x7E]+$'
 
636
            self.security_group_api.validate_property(group_name, 'name',
 
637
                                                      allowed)
 
638
 
 
639
        group_ref = self.security_group_api.create(context, group_name,
 
640
                                                   group_description)
804
641
 
805
642
        return {'securityGroupSet': [self._format_security_group(context,
806
643
                                                                 group_ref)]}
810
647
        if not group_name and not group_id:
811
648
            err = _("Not enough parameters, need group_name or group_id")
812
649
            raise exception.EC2APIError(err)
813
 
        notfound = exception.SecurityGroupNotFound
814
 
        if group_name:
815
 
            security_group = db.security_group_get_by_name(context,
816
 
                                                           context.project_id,
817
 
                                                           group_name)
818
 
            if not security_group:
819
 
                raise notfound(security_group_id=group_name)
820
 
        elif group_id:
821
 
            security_group = db.security_group_get(context, group_id)
822
 
            if not security_group:
823
 
                raise notfound(security_group_id=group_id)
824
 
        if db.security_group_in_use(context, security_group.id):
825
 
            raise exception.InvalidGroup(reason="In Use")
826
 
 
827
 
        # Get reservations
828
 
        try:
829
 
            reservations = QUOTAS.reserve(context, security_groups=-1)
830
 
        except Exception:
831
 
            reservations = None
832
 
            LOG.exception(_("Failed to update usages deallocating "
833
 
                            "security group"))
834
 
 
835
 
        LOG.audit(_("Delete security group %s"), group_name, context=context)
836
 
        db.security_group_destroy(context, security_group.id)
837
 
 
838
 
        self.sgh.trigger_security_group_destroy_refresh(context,
839
 
                                                        security_group.id)
840
 
 
841
 
        # Commit the reservations
842
 
        if reservations:
843
 
            QUOTAS.commit(context, reservations)
 
650
 
 
651
        security_group = self.security_group_api.get(context, group_name,
 
652
                                                     group_id)
 
653
 
 
654
        self.security_group_api.destroy(context, security_group)
844
655
 
845
656
        return True
846
657
 
856
667
        instance_id = ec2utils.ec2_id_to_id(ec2_id)
857
668
        instance = self.compute_api.get(context, instance_id)
858
669
        output = self.compute_api.get_console_output(context, instance)
859
 
        now = utils.utcnow()
 
670
        now = timeutils.utcnow()
860
671
        return {"InstanceId": ec2_id,
861
672
                "Timestamp": now,
862
673
                "output": base64.b64encode(output)}
1301
1112
                " instance %(instance_id)s") % locals(), context=context)
1302
1113
        instance_id = ec2utils.ec2_id_to_id(instance_id)
1303
1114
        instance = self.compute_api.get(context, instance_id)
 
1115
 
 
1116
        cached_ipinfo = ec2utils.get_ip_info_for_instance(context, instance)
 
1117
        fixed_ips = cached_ipinfo['fixed_ips'] + cached_ipinfo['fixed_ip6s']
 
1118
        if not fixed_ips:
 
1119
            msg = _('Unable to associate IP Address, no fixed_ips.')
 
1120
            raise exception.EC2APIError(msg)
 
1121
 
 
1122
        # TODO(tr3buchet): this will associate the floating IP with the
 
1123
        # first fixed_ip an instance has. This should be
 
1124
        # changed to support specifying a particular fixed_ip if
 
1125
        # multiple exist but this may not apply to ec2..
 
1126
        if len(fixed_ips) > 1:
 
1127
            msg = _('multiple fixed_ips exist, using the first: %s')
 
1128
            LOG.warning(msg, fixed_ips[0])
 
1129
 
1304
1130
        try:
1305
 
            self.compute_api.associate_floating_ip(context,
1306
 
                                                   instance,
1307
 
                                                   address=public_ip)
1308
 
            return {'return': "true"}
1309
 
        except exception.FloatingIpNotFound:
1310
 
            raise exception.EC2APIError(_('Unable to associate IP Address.'))
 
1131
            self.network_api.associate_floating_ip(context, instance,
 
1132
                                  floating_address=public_ip,
 
1133
                                  fixed_address=fixed_ips[0])
 
1134
            return {'return': 'true'}
 
1135
        except exception.FloatingIpAssociated:
 
1136
            msg = _('Floating ip is already associated.')
 
1137
            raise exception.EC2APIError(msg)
 
1138
        except exception.NoFloatingIpInterface:
 
1139
            msg = _('l3driver call to add floating ip failed.')
 
1140
            raise exception.EC2APIError(msg)
 
1141
        except Exception:
 
1142
            msg = _('Error, unable to associate floating ip.')
 
1143
            LOG.exception(msg)
 
1144
            raise exception.EC2APIError(msg)
1311
1145
 
1312
1146
    def disassociate_address(self, context, public_ip, **kwargs):
 
1147
        instance_id = self.network_api.get_instance_id_by_floating_address(
 
1148
                                                         context, public_ip)
 
1149
        instance = self.compute_api.get(context, instance_id)
1313
1150
        LOG.audit(_("Disassociate address %s"), public_ip, context=context)
1314
 
        self.network_api.disassociate_floating_ip(context, address=public_ip)
 
1151
        try:
 
1152
            self.network_api.disassociate_floating_ip(context, instance,
 
1153
                                                      address=public_ip)
 
1154
        except exception.FloatingIpNotAssociated:
 
1155
            msg = _('Floating ip is not associated.')
 
1156
            raise exception.EC2APIError(msg)
 
1157
 
1315
1158
        return {'return': "true"}
1316
1159
 
1317
1160
    def run_instances(self, context, **kwargs):
1696
1539
            self.compute_api.start(context, instance_id=instance_id)
1697
1540
 
1698
1541
        return {'imageId': image_id}
 
1542
 
 
1543
 
 
1544
class CloudSecurityGroupAPI(compute.api.SecurityGroupAPI):
 
1545
    @staticmethod
 
1546
    def raise_invalid_property(msg):
 
1547
        raise exception.InvalidParameterValue(err=msg)
 
1548
 
 
1549
    @staticmethod
 
1550
    def raise_group_already_exists(msg):
 
1551
        raise exception.EC2APIError(message=msg)
 
1552
 
 
1553
    @staticmethod
 
1554
    def raise_invalid_group(msg):
 
1555
        raise exception.InvalidGroup(reason=msg)
 
1556
 
 
1557
    @staticmethod
 
1558
    def raise_invalid_cidr(cidr, decoding_exception=None):
 
1559
        if decoding_exception:
 
1560
            raise decoding_exception
 
1561
        else:
 
1562
            raise exception.EC2APIError(_("Invalid CIDR"))
 
1563
 
 
1564
    @staticmethod
 
1565
    def raise_over_quota(msg):
 
1566
        raise exception.EC2APIError(message=msg)
 
1567
 
 
1568
    @staticmethod
 
1569
    def raise_not_found(msg):
 
1570
        pass