github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/logfwd/origin.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package logfwd
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/version"
    11  	"gopkg.in/juju/names.v2"
    12  )
    13  
    14  // canonicalPEN is the IANA-registered Private Enterprise Number
    15  // assigned to Canonical. Among other things, this is used in RFC 5424
    16  // structured data.
    17  //
    18  // See https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers.
    19  const canonicalPEN = 28978
    20  
    21  // These are the recognized origin types.
    22  const (
    23  	OriginTypeUnknown OriginType = 0
    24  	OriginTypeUser               = iota
    25  	OriginTypeMachine
    26  	OriginTypeUnit
    27  )
    28  
    29  var originTypes = map[OriginType]string{
    30  	OriginTypeUnknown: "unknown",
    31  	OriginTypeUser:    names.UserTagKind,
    32  	OriginTypeMachine: names.MachineTagKind,
    33  	OriginTypeUnit:    names.UnitTagKind,
    34  }
    35  
    36  // OriginType is the "enum" type for the different kinds of log record
    37  // origin.
    38  type OriginType int
    39  
    40  // ParseOriginType converts a string to an OriginType or fails if
    41  // not able. It round-trips with String().
    42  func ParseOriginType(value string) (OriginType, error) {
    43  	for ot, str := range originTypes {
    44  		if value == str {
    45  			return ot, nil
    46  		}
    47  	}
    48  	const originTypeInvalid OriginType = -1
    49  	return originTypeInvalid, errors.Errorf("unrecognized origin type %q", value)
    50  }
    51  
    52  // String returns a string representation of the origin type.
    53  func (ot OriginType) String() string {
    54  	return originTypes[ot]
    55  }
    56  
    57  // Validate ensures that the origin type is correct.
    58  func (ot OriginType) Validate() error {
    59  	// As noted above, typedef'ing int means that the use of int
    60  	// literals or explicit type conversion could result in unsupported
    61  	// "enum" values. Otherwise OriginType would not need this method.
    62  	if _, ok := originTypes[ot]; !ok {
    63  		return errors.NewNotValid(nil, "unsupported origin type")
    64  	}
    65  	return nil
    66  }
    67  
    68  // ValidateName ensures that the given origin name is valid within the
    69  // context of the origin type.
    70  func (ot OriginType) ValidateName(name string) error {
    71  	switch ot {
    72  	case OriginTypeUnknown:
    73  		if name != "" {
    74  			return errors.NewNotValid(nil, "origin name must not be set if type is unknown")
    75  		}
    76  	case OriginTypeUser:
    77  		if !names.IsValidUser(name) {
    78  			return errors.NewNotValid(nil, "bad user name")
    79  		}
    80  	case OriginTypeMachine:
    81  		if !names.IsValidMachine(name) {
    82  			return errors.NewNotValid(nil, "bad machine name")
    83  		}
    84  	case OriginTypeUnit:
    85  		if !names.IsValidUnit(name) {
    86  			return errors.NewNotValid(nil, "bad unit name")
    87  		}
    88  	}
    89  	return nil
    90  }
    91  
    92  // Origin describes what created the record.
    93  type Origin struct {
    94  	// ControllerUUID is the ID of the Juju controller under which the
    95  	// record originated.
    96  	ControllerUUID string
    97  
    98  	// ModelUUID is the ID of the Juju model under which the record
    99  	// originated.
   100  	ModelUUID string
   101  
   102  	// Hostname identifies the host where the record originated.
   103  	Hostname string
   104  
   105  	// Type identifies the kind of thing that generated the record.
   106  	Type OriginType
   107  
   108  	// Name identifies the thing that generated the record.
   109  	Name string
   110  
   111  	// Software identifies the running software that created the record.
   112  	Software Software
   113  }
   114  
   115  // OriginForMachineAgent populates a new origin for the agent.
   116  func OriginForMachineAgent(tag names.MachineTag, controller, model string, ver version.Number) Origin {
   117  	return originForAgent(OriginTypeMachine, tag, controller, model, ver)
   118  }
   119  
   120  // OriginForUnitAgent populates a new origin for the agent.
   121  func OriginForUnitAgent(tag names.UnitTag, controller, model string, ver version.Number) Origin {
   122  	return originForAgent(OriginTypeUnit, tag, controller, model, ver)
   123  }
   124  
   125  func originForAgent(oType OriginType, tag names.Tag, controller, model string, ver version.Number) Origin {
   126  	origin := originForJuju(oType, tag.Id(), controller, model, ver)
   127  	origin.Hostname = fmt.Sprintf("%s.%s", tag, model)
   128  	origin.Software.Name = fmt.Sprintf("jujud-%s-agent", tag.Kind())
   129  	return origin
   130  }
   131  
   132  // OriginForJuju populates a new origin for the juju client.
   133  func OriginForJuju(tag names.Tag, controller, model string, ver version.Number) (Origin, error) {
   134  	oType, err := ParseOriginType(tag.Kind())
   135  	if err != nil {
   136  		return Origin{}, errors.Annotate(err, "invalid tag")
   137  	}
   138  	return originForJuju(oType, tag.Id(), controller, model, ver), nil
   139  }
   140  
   141  func originForJuju(oType OriginType, name, controller, model string, ver version.Number) Origin {
   142  	return Origin{
   143  		ControllerUUID: controller,
   144  		ModelUUID:      model,
   145  		Type:           oType,
   146  		Name:           name,
   147  		Software: Software{
   148  			PrivateEnterpriseNumber: canonicalPEN,
   149  			Name:    "juju",
   150  			Version: ver,
   151  		},
   152  	}
   153  }
   154  
   155  // Validate ensures that the origin is correct.
   156  func (o Origin) Validate() error {
   157  	if o.ControllerUUID == "" {
   158  		return errors.NewNotValid(nil, "empty ControllerUUID")
   159  	}
   160  	if !names.IsValidModel(o.ControllerUUID) {
   161  		return errors.NewNotValid(nil, fmt.Sprintf("ControllerUUID %q not a valid UUID", o.ControllerUUID))
   162  	}
   163  
   164  	if o.ModelUUID == "" {
   165  		return errors.NewNotValid(nil, "empty ModelUUID")
   166  	}
   167  	if !names.IsValidModel(o.ModelUUID) {
   168  		return errors.NewNotValid(nil, fmt.Sprintf("ModelUUID %q not a valid UUID", o.ModelUUID))
   169  	}
   170  
   171  	if err := o.Type.Validate(); err != nil {
   172  		return errors.Annotate(err, "invalid Type")
   173  	}
   174  
   175  	if o.Name == "" && o.Type != OriginTypeUnknown {
   176  		return errors.NewNotValid(nil, "empty Name")
   177  	}
   178  	if err := o.Type.ValidateName(o.Name); err != nil {
   179  		return errors.Annotatef(err, "invalid Name %q", o.Name)
   180  	}
   181  
   182  	if !o.Software.isZero() {
   183  		if err := o.Software.Validate(); err != nil {
   184  			return errors.Annotate(err, "invalid Software")
   185  		}
   186  	}
   187  
   188  	return nil
   189  }
   190  
   191  // Software describes a running application.
   192  type Software struct {
   193  	// PrivateEnterpriseNumber is the IANA-registered "SMI Network
   194  	// Management Private Enterprise Code" for the software's vendor.
   195  	//
   196  	// See https://tools.ietf.org/html/rfc5424#section-7.2.2.
   197  	PrivateEnterpriseNumber int
   198  
   199  	// Name identifies the software (relative to the vendor).
   200  	Name string
   201  
   202  	// Version is the software's version.
   203  	Version version.Number
   204  }
   205  
   206  func (sw Software) isZero() bool {
   207  	if sw.PrivateEnterpriseNumber > 0 {
   208  		return false
   209  	}
   210  	if sw.Name != "" {
   211  		return false
   212  	}
   213  	if sw.Version != version.Zero {
   214  		return false
   215  	}
   216  	return true
   217  }
   218  
   219  // Validate ensures that the software info is correct.
   220  func (sw Software) Validate() error {
   221  	if sw.PrivateEnterpriseNumber <= 0 {
   222  		return errors.NewNotValid(nil, "missing PrivateEnterpriseNumber")
   223  	}
   224  	if sw.Name == "" {
   225  		return errors.NewNotValid(nil, "empty Name")
   226  	}
   227  	if sw.Version == version.Zero {
   228  		return errors.NewNotValid(nil, "empty Version")
   229  	}
   230  	return nil
   231  }